Skip to content

Commit cf6e5e1

Browse files
Compute checksums for reusable workflows
Expand the initialization, updating, and verification to cover reusable workflows in the checksums, including the transitive actions used in those reusable workflows. A reusable workflow is a workflow that is referenced in the `uses:` value of a job and is simply some other workflow that will run when that job would [1]. Also, correct a typo in the output when there verification detected problems. -- 1. https://docs.github.com/en/actions/sharing-automations/reusing-workflows
1 parent fa1c90c commit cf6e5e1

File tree

19 files changed

+626
-93
lines changed

19 files changed

+626
-93
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ Versioning].
1515
### Enhancements
1616

1717
- Correct typo in the `ghasum help verify` output.
18+
- Correct typo in the `ghasum verify` output.
1819
- Enable cache eviction on `ghasum init`.
1920
- Ensure `ghasum verify` outcome is linked to `gha.sum` content.
21+
- Include reusable workflows in `gha.sum`, including transitive actions used in
22+
reusable workflows.
2023

2124
## [v0.4.0] - 2025-04-27
2225

SPECIFICATION.md

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,25 @@ Redundant checksums are ignored by this process.
9090
### Collecting Actions
9191

9292
To determine the set of actions a target depends on, first find all `uses:`
93-
entries in the target. For a repository this covers all workflows in the
94-
workflows directory, otherwise it covers only the target.
93+
entries in the target, both at the step- and job-level. For a repository this,
94+
covers all workflows in the workflows directory, otherwise it covers only the
95+
target.
9596

9697
For each `uses:` value, excluding the list below, it is added to the set. If the
97-
`-no-transitive` option is NOT set the repository declared by the `uses:` value
98-
is fetched. The action manifest at the path specified in the `uses:` value is
99-
parsed for additional `uses:` values. For each of these transitive `uses:`
100-
values, this process is repeated.
98+
`-no-transitive` option is set this constitutes the set of actions. Otherwise,
99+
transitive actions must be collected too.
100+
101+
For step-level `uses:` values, the action manifest of the declared action is
102+
parsed for (transitive) actions. This concerns the manifest at the path declared
103+
in the `uses:` value. If multiple actions from the same repository are used,
104+
each action's manifest must be handled.
105+
106+
For job-level `uses:` values, the workflow of the declared reusable workflow is
107+
parsed for (transitive) actions. This concerns only the workflow at the path
108+
declared in the `uses:` value. If multiple reusable workflow from the same
109+
repository are used, each workflow must be handled.
110+
111+
This process is repeated or each transitive `uses:` value.
101112

102113
The following `uses:` values are to be excluded from the set of actions a
103114
repository depends on.
@@ -109,6 +120,14 @@ repository depends on.
109120
- uses: ./.github/actions/hello-world-action
110121
```
111122
123+
- Reusable workflows in the same repository as the workflow. Example:
124+
125+
```yaml
126+
jobs:
127+
example:
128+
uses: ./.github/workflows/reusable.yml
129+
```
130+
112131
- Docker Hub Actions ([#216]). Examples:
113132
114133
```yaml
@@ -118,19 +137,6 @@ repository depends on.
118137
- uses: docker://gcr.io/cloud-builders/gradle
119138
```
120139
121-
- Reusable workflows ([#215]). Examples:
122-
123-
```yaml
124-
jobs:
125-
call-workflow-1-in-local-repo:
126-
uses: octo-org/this-repo/.github/workflows/workflow-1.yml@172239021f7ba04fe7327647b213799853a9eb89
127-
call-workflow-2-in-local-repo:
128-
uses: ./.github/workflows/workflow-2.yml
129-
call-workflow-in-another-repo:
130-
uses: octo-org/another-repo/.github/workflows/workflow.yml@v1
131-
```
132-
133-
[#215]: https://github.com/chains-project/ghasum/issues/215
134140
[#216]: https://github.com/chains-project/ghasum/issues/216
135141
136142
### Computing Checksums

cmd/ghasum/verify.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ func cmdVerify(argv []string) error {
103103

104104
if cnt := len(problems); cnt > 0 {
105105
var sb strings.Builder
106-
sb.WriteString(fmt.Sprintf("%d problems(s) occurred during validation:\n", cnt))
106+
sb.WriteString(fmt.Sprintf("%d problem(s) occurred during validation:\n", cnt))
107107
for _, problem := range problems {
108108
sb.WriteString(fmt.Sprintf(" %s\n", problem))
109109
}

internal/gha/actions.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,18 @@ func actionsInWorkflows(workflows []workflow) ([]GitHubAction, error) {
4848
if err != nil {
4949
return nil, err
5050
}
51+
52+
if job.Uses != "" {
53+
action, err := parseUses(job.Uses)
54+
if errors.Is(err, ErrLocalAction) {
55+
continue
56+
} else if err != nil {
57+
return nil, err
58+
}
59+
60+
action.Kind = ReusableWorkflow
61+
unique[actionId(action)] = action
62+
}
5163
}
5264
}
5365

@@ -68,13 +80,17 @@ func actionsInSteps(steps []step, m map[string]GitHubAction) error {
6880
return err
6981
}
7082

71-
id := fmt.Sprintf("%s%s%s%s", action.Owner, action.Project, action.Path, action.Ref)
72-
m[id] = action
83+
action.Kind = Action
84+
m[actionId(action)] = action
7385
}
7486

7587
return nil
7688
}
7789

90+
func actionId(action GitHubAction) string {
91+
return fmt.Sprintf("%s%s%s%s", action.Owner, action.Project, action.Path, action.Ref)
92+
}
93+
7894
func workflowsInRepo(repo fs.FS) ([]workflowFile, error) {
7995
workflows := make([]workflowFile, 0)
8096
walk := func(entryPath string, entry fs.DirEntry, err error) error {

internal/gha/actions_test.go

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,30 @@ func TestActionsInWorkflows(t *testing.T) {
360360
},
361361
want: 1,
362362
},
363+
"job with local reusable workflow": {
364+
in: []workflow{
365+
{
366+
Jobs: map[string]job{
367+
"example": {
368+
Uses: "./.github/workflows/workflow-2.yml",
369+
},
370+
},
371+
},
372+
},
373+
want: 0,
374+
},
375+
"job with external reusable workflow": {
376+
in: []workflow{
377+
{
378+
Jobs: map[string]job{
379+
"example": {
380+
Uses: "octo-org/another-repo/.github/workflows/workflow.yml@v1",
381+
},
382+
},
383+
},
384+
},
385+
want: 1,
386+
},
363387
}
364388

365389
for name, tt := range testCases {
@@ -386,7 +410,18 @@ func TestActionsInWorkflows(t *testing.T) {
386410
}
387411

388412
testCases := map[string]TestCase{
389-
"invalid uses value": {
413+
"invalid job uses value": {
414+
in: []workflow{
415+
{
416+
Jobs: map[string]job{
417+
"example": {
418+
Uses: "this isn't a reusable workflow",
419+
},
420+
},
421+
},
422+
},
423+
},
424+
"invalid step uses value": {
390425
in: []workflow{
391426
{
392427
Jobs: map[string]job{

internal/gha/gha.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,32 @@ type GitHubAction struct {
3636
// Ref is the git ref (branch, tag, commit SHA), also known as version, of the
3737
// GitHub Action.
3838
Ref string
39+
40+
// Kind is the [ActionKind] of the GitHub Action.
41+
Kind ActionKind
3942
}
4043

44+
// ActionKind identifies the type of reusable component in GitHub Action.
45+
type ActionKind uint8
46+
47+
const (
48+
_ ActionKind = iota
49+
50+
// Action represent a GitHub Actions component that is an "action".
51+
//
52+
// An action is a path in a repository that has an Action manifest, i.e. a
53+
// file named either action.yml, action.yaml, or Dockerfile. These are used
54+
// in the `uses:` value of steps.
55+
Action
56+
57+
// ReusableWorkflow represent a GitHub Actions component that is a "reusable
58+
// workflow".
59+
//
60+
// A reusable workflow is a workflow in a repository with the appropriate
61+
// workflow trigger. These are used in the `uses:` value of workflow jobs.
62+
ReusableWorkflow
63+
)
64+
4165
// WorkflowsPath is the relative path to the GitHub Actions workflow directory.
4266
var WorkflowsPath = filepath.Join(".github", "workflows")
4367

@@ -139,3 +163,11 @@ func ManifestActions(repo fs.FS, path string) ([]GitHubAction, error) {
139163

140164
return actions, nil
141165
}
166+
167+
func (a GitHubAction) String() string {
168+
if a.Path == "" {
169+
return fmt.Sprintf("%s/%s@%s", a.Owner, a.Project, a.Ref)
170+
} else {
171+
return fmt.Sprintf("%s/%s/%s@%s", a.Owner, a.Project, a.Path, a.Ref)
172+
}
173+
}

0 commit comments

Comments
 (0)