0
# GitHub Actions
1
2
Utilities for GitHub Actions workflows including environment variable management, path manipulation, and logging command output. These functions help create GitHub Actions that integrate with the workflow environment.
3
4
## Capabilities
5
6
### Workspace Access
7
8
Access the GitHub Actions workspace directory.
9
10
```python { .api }
11
import pathlib
12
13
def workspace() -> pathlib.Path:
14
"""
15
Return the action workspace as a pathlib.Path object.
16
17
Returns:
18
- Path object pointing to the workspace directory
19
20
Note: Uses the GITHUB_WORKSPACE environment variable.
21
This function is cached with @functools.lru_cache(maxsize=1).
22
"""
23
```
24
25
### Event Data Access
26
27
Access the webhook event that triggered the workflow.
28
29
```python { .api }
30
def event() -> Any:
31
"""
32
Return the webhook event data for the running action.
33
34
Returns:
35
- Parsed JSON data from the event that triggered the workflow
36
37
Note: Uses the GITHUB_EVENT_PATH environment variable.
38
This function is cached with @functools.lru_cache(maxsize=1).
39
"""
40
```
41
42
### Workflow Commands
43
44
Issue logging commands that are processed by the GitHub Actions runner.
45
46
```python { .api }
47
def command(cmd: str, data: str = "", **parameters: str) -> None:
48
"""
49
Issue a logging command that will be processed by the Actions runner.
50
51
Parameters:
52
- cmd: Command name (e.g., "error", "warning", "notice", "debug")
53
- data: Command data/message
54
- **parameters: Command parameters (e.g., file, line, col, title)
55
56
Examples:
57
- command("error", "Something went wrong", file="app.py", line="42")
58
- command("warning", "Deprecated function used")
59
- command("notice", "Build completed successfully")
60
"""
61
```
62
63
### Environment Management
64
65
Manage environment variables for the current and future actions.
66
67
```python { .api }
68
def setenv(name: str, value: str) -> None:
69
"""
70
Create or update an environment variable.
71
The change applies to this action and future actions in the job.
72
73
Parameters:
74
- name: Environment variable name
75
- value: Environment variable value (can be multiline)
76
77
Note: Uses the GITHUB_ENV environment file for persistence.
78
"""
79
80
def addpath(path: Union[str, "os.PathLike[str]"]) -> None:
81
"""
82
Prepend a directory to the PATH environment variable.
83
This affects this action and all subsequent actions in the current job.
84
85
Parameters:
86
- path: Directory path to add to PATH
87
88
Note: Uses the GITHUB_PATH environment file for persistence.
89
"""
90
```
91
92
## Usage Examples
93
94
### Basic Action Script
95
96
```python
97
#!/usr/bin/env python3
98
import gidgethub.actions
99
import subprocess
100
import sys
101
102
def main():
103
# Get workspace directory
104
workspace = gidgethub.actions.workspace()
105
print(f"Working in: {workspace}")
106
107
# Get event data
108
event_data = gidgethub.actions.event()
109
event_name = event_data.get('action', 'unknown')
110
111
gidgethub.actions.command("notice", f"Processing {event_name} event")
112
113
# Example: Run tests
114
try:
115
result = subprocess.run(
116
["python", "-m", "pytest"],
117
cwd=workspace,
118
capture_output=True,
119
text=True
120
)
121
122
if result.returncode == 0:
123
gidgethub.actions.command("notice", "Tests passed successfully")
124
else:
125
gidgethub.actions.command("error", f"Tests failed: {result.stderr}")
126
sys.exit(1)
127
128
except Exception as e:
129
gidgethub.actions.command("error", f"Failed to run tests: {e}")
130
sys.exit(1)
131
132
if __name__ == "__main__":
133
main()
134
```
135
136
### Environment Variable Management
137
138
```python
139
import gidgethub.actions
140
import os
141
142
def setup_build_environment():
143
# Set build configuration
144
gidgethub.actions.setenv("BUILD_TYPE", "release")
145
gidgethub.actions.setenv("OPTIMIZATION_LEVEL", "3")
146
147
# Set multiline environment variable
148
config_json = """{
149
"api_url": "https://api.example.com",
150
"timeout": 30,
151
"retries": 3
152
}"""
153
gidgethub.actions.setenv("BUILD_CONFIG", config_json)
154
155
# Add custom tools to PATH
156
tools_dir = gidgethub.actions.workspace() / "tools" / "bin"
157
gidgethub.actions.addpath(tools_dir)
158
159
# Verify environment
160
print(f"BUILD_TYPE: {os.environ.get('BUILD_TYPE')}")
161
print(f"PATH includes tools: {str(tools_dir) in os.environ.get('PATH', '')}")
162
163
setup_build_environment()
164
```
165
166
### Error Reporting with File Locations
167
168
```python
169
import gidgethub.actions
170
import ast
171
import sys
172
173
def lint_python_file(file_path):
174
"""Lint a Python file and report errors with locations."""
175
try:
176
with open(file_path, 'r') as f:
177
source = f.read()
178
179
# Parse the file
180
ast.parse(source)
181
gidgethub.actions.command("notice", f"β {file_path} is valid Python")
182
183
except SyntaxError as e:
184
# Report syntax error with file location
185
gidgethub.actions.command(
186
"error",
187
f"Syntax error: {e.msg}",
188
file=str(file_path),
189
line=str(e.lineno),
190
col=str(e.offset) if e.offset else "1"
191
)
192
return False
193
194
except Exception as e:
195
gidgethub.actions.command("error", f"Failed to process {file_path}: {e}")
196
return False
197
198
return True
199
200
def main():
201
workspace = gidgethub.actions.workspace()
202
python_files = list(workspace.glob("**/*.py"))
203
204
gidgethub.actions.command("notice", f"Linting {len(python_files)} Python files")
205
206
all_valid = True
207
for py_file in python_files:
208
if not lint_python_file(py_file):
209
all_valid = False
210
211
if not all_valid:
212
gidgethub.actions.command("error", "Linting failed")
213
sys.exit(1)
214
else:
215
gidgethub.actions.command("notice", "All files passed linting")
216
217
if __name__ == "__main__":
218
main()
219
```
220
221
### Processing Webhook Events
222
223
```python
224
import gidgethub.actions
225
226
def process_pull_request_event():
227
event = gidgethub.actions.event()
228
229
if event.get('action') == 'opened':
230
pr = event['pull_request']
231
232
gidgethub.actions.command(
233
"notice",
234
f"New PR opened: {pr['title']}",
235
title="Pull Request Opened"
236
)
237
238
# Check if PR affects specific files
239
changed_files = [] # You'd get this from the GitHub API
240
if any(f.endswith('.py') for f in changed_files):
241
gidgethub.actions.setenv("RUN_PYTHON_TESTS", "true")
242
243
if any(f.endswith(('.js', '.ts')) for f in changed_files):
244
gidgethub.actions.setenv("RUN_JS_TESTS", "true")
245
246
elif event.get('action') == 'closed':
247
pr = event['pull_request']
248
if pr['merged']:
249
gidgethub.actions.command("notice", f"PR merged: {pr['title']}")
250
else:
251
gidgethub.actions.command("notice", f"PR closed: {pr['title']}")
252
253
process_pull_request_event()
254
```
255
256
### Custom Action with Inputs and Outputs
257
258
```python
259
import gidgethub.actions
260
import os
261
import json
262
263
def parse_inputs():
264
"""Parse action inputs from environment variables."""
265
return {
266
'target': os.environ.get('INPUT_TARGET', 'main'),
267
'dry_run': os.environ.get('INPUT_DRY_RUN', 'false').lower() == 'true',
268
'config_file': os.environ.get('INPUT_CONFIG_FILE', 'config.json')
269
}
270
271
def set_outputs(**outputs):
272
"""Set action outputs."""
273
for key, value in outputs.items():
274
# GitHub Actions uses special echo commands for outputs
275
print(f"::set-output name={key}::{value}")
276
277
def main():
278
inputs = parse_inputs()
279
280
gidgethub.actions.command(
281
"notice",
282
f"Running with target: {inputs['target']}, dry_run: {inputs['dry_run']}"
283
)
284
285
workspace = gidgethub.actions.workspace()
286
config_path = workspace / inputs['config_file']
287
288
if not config_path.exists():
289
gidgethub.actions.command(
290
"error",
291
f"Config file not found: {config_path}",
292
file=str(config_path)
293
)
294
return 1
295
296
# Process configuration
297
try:
298
with open(config_path) as f:
299
config = json.load(f)
300
301
# Set outputs for other actions to use
302
set_outputs(
303
config_valid="true",
304
target_branch=inputs['target'],
305
config_version=config.get('version', 'unknown')
306
)
307
308
gidgethub.actions.command("notice", "Action completed successfully")
309
return 0
310
311
except Exception as e:
312
gidgethub.actions.command("error", f"Failed to process config: {e}")
313
return 1
314
315
if __name__ == "__main__":
316
exit_code = main()
317
exit(exit_code)
318
```
319
320
### Integration with gidgethub API Client
321
322
```python
323
import asyncio
324
import aiohttp
325
import gidgethub.actions
326
from gidgethub.aiohttp import GitHubAPI
327
328
async def comment_on_pr():
329
"""Add a comment to the PR that triggered this action."""
330
event = gidgethub.actions.event()
331
332
if event.get('action') != 'opened':
333
gidgethub.actions.command("notice", "Not a PR opened event, skipping")
334
return
335
336
# Get GitHub token from environment
337
github_token = os.environ.get('GITHUB_TOKEN')
338
if not github_token:
339
gidgethub.actions.command("error", "GITHUB_TOKEN not set")
340
return
341
342
repository = event['repository']['full_name']
343
pr_number = event['pull_request']['number']
344
345
async with aiohttp.ClientSession() as session:
346
gh = GitHubAPI(session, "pr-commenter/1.0", oauth_token=github_token)
347
348
try:
349
# Add comment to PR
350
await gh.post(
351
f"/repos/{repository}/issues/{pr_number}/comments",
352
data={
353
"body": "π Thanks for opening this PR! Our automated checks are running."
354
}
355
)
356
357
gidgethub.actions.command("notice", f"Added comment to PR #{pr_number}")
358
359
except Exception as e:
360
gidgethub.actions.command("error", f"Failed to add comment: {e}")
361
362
# Run the async function
363
asyncio.run(comment_on_pr())
364
```
365
366
## GitHub Actions Environment Variables
367
368
Common environment variables available in GitHub Actions:
369
370
- `GITHUB_WORKSPACE`: The workspace directory path
371
- `GITHUB_EVENT_PATH`: Path to the webhook event JSON file
372
- `GITHUB_TOKEN`: GitHub token for API access
373
- `GITHUB_REPOSITORY`: Repository name (owner/name)
374
- `GITHUB_REF`: Git ref that triggered the workflow
375
- `GITHUB_SHA`: Commit SHA that triggered the workflow
376
- `GITHUB_ACTOR`: Username of the user that triggered the workflow
377
- `GITHUB_WORKFLOW`: Name of the workflow
378
- `GITHUB_RUN_ID`: Unique identifier for the workflow run
379
- `GITHUB_RUN_NUMBER`: Sequential number for the workflow run
380
381
## Workflow Command Reference
382
383
Common workflow commands you can use with the `command()` function:
384
385
- `error`: Create an error message
386
- `warning`: Create a warning message
387
- `notice`: Create a notice message
388
- `debug`: Create a debug message
389
- `group`: Start a collapsible group
390
- `endgroup`: End a collapsible group
391
- `save-state`: Save state for post-action
392
- `set-output`: Set action output (deprecated, use environment files)
393
394
## Types
395
396
```python { .api }
397
import pathlib
398
import os
399
from typing import Any, Union
400
401
# Path types for workspace and file operations
402
PathLike = Union[str, "os.PathLike[str]"]
403
WorkspacePath = pathlib.Path
404
405
# Event data type (parsed JSON)
406
EventData = Any # Dictionary containing webhook event data
407
```