Skip to content

Commit 606b252

Browse files
authored
amplify-preview: custom action to trigger amplify previews (#287)
* Initial version of `amplify-preview` action * Add readme * Dependency fixes * Add CICD files * Address PR feedback * PR feedback, part 2
1 parent 0f1f9de commit 606b252

16 files changed

+2478
-6
lines changed
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
name: Amplify Preview CD
3+
4+
on:
5+
push:
6+
branches:
7+
- main
8+
paths:
9+
- tools/amplify-preview
10+
tags:
11+
- "tools/amplify-preview/v[0-9]+.[0-9]+.[0-9]+**"
12+
pull_request:
13+
paths:
14+
- tools/amplify-preview/workflows/cd.yaml
15+
- .github/workflows/amplify-preview-cd.yaml
16+
- .github/workflows/reusable-cd.yaml
17+
18+
jobs:
19+
release:
20+
uses: ./.github/workflows/reusable-cd.yaml
21+
permissions:
22+
contents: write
23+
packages: write
24+
with:
25+
tool-directory: ./tools/amplify-preview
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
name: Amplify Preview CI
3+
4+
on:
5+
pull_request:
6+
7+
jobs:
8+
release:
9+
uses: ./.github/workflows/reusable-ci.yaml
10+
permissions:
11+
contents: write
12+
packages: write
13+
with:
14+
tool-directory: ./tools/amplify-preview

libs/github/comment.go

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"errors"
6+
"strings"
7+
8+
"github.com/google/go-github/v63/github"
9+
)
10+
11+
var (
12+
ErrCommentNotFound = errors.New("comment not found")
13+
)
14+
15+
// IssueIdentifier represents an issue or PR on GitHub
16+
type IssueIdentifier struct {
17+
Owner string
18+
Repo string
19+
Number int
20+
}
21+
22+
// CommentTraits defines optional traits to filter comments.
23+
// Every trait (if non-empty-string) is matched with an "AND" operator
24+
type CommentTraits struct {
25+
BodyContains string
26+
UserLogin string
27+
}
28+
29+
// FindCommentByTraits searches for a comment in an PR or issue based on specified traits
30+
func (c *Client) FindCommentByTraits(ctx context.Context, issue IssueIdentifier, targetComment CommentTraits) (*github.IssueComment, error) {
31+
comments, _, err := c.client.Issues.ListComments(ctx, issue.Owner, issue.Repo, issue.Number, nil)
32+
if err != nil {
33+
return nil, err
34+
}
35+
36+
for _, c := range comments {
37+
matcher := true
38+
if targetComment.UserLogin != "" {
39+
matcher = matcher && c.User.GetLogin() == targetComment.UserLogin
40+
}
41+
42+
if targetComment.BodyContains != "" {
43+
matcher = matcher && strings.Contains(c.GetBody(), targetComment.BodyContains)
44+
}
45+
46+
if matcher {
47+
return c, nil
48+
}
49+
}
50+
51+
return nil, ErrCommentNotFound
52+
}
53+
54+
// CreateComment creates a new comment on an issue or PR
55+
func (c *Client) CreateComment(ctx context.Context, issue IssueIdentifier, commentBody string) error {
56+
_, _, err := c.client.Issues.CreateComment(ctx, issue.Owner, issue.Repo, issue.Number, &github.IssueComment{
57+
Body: &commentBody,
58+
})
59+
60+
return err
61+
}
62+
63+
// UpdateComment updates an existing comment on an issue or PR
64+
func (c *Client) UpdateComment(ctx context.Context, issue IssueIdentifier, commentId int64, commentBody string) error {
65+
_, _, err := c.client.Issues.EditComment(ctx, issue.Owner, issue.Repo, commentId, &github.IssueComment{
66+
Body: &commentBody,
67+
})
68+
69+
return err
70+
}

libs/go.mod

+5-2
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@ module github.com/gravitational/shared-workflows/libs
33
go 1.22.4
44

55
require (
6-
github.com/cli/go-gh/v2 v2.9.0
6+
github.com/cli/go-gh/v2 v2.11.1
77
github.com/google/go-github/v63 v63.0.0
88
github.com/gravitational/trace v1.4.0
9+
github.com/stretchr/testify v1.8.3
910
golang.org/x/oauth2 v0.21.0
1011
)
1112

1213
require (
13-
github.com/cli/safeexec v1.0.0 // indirect
14+
github.com/cli/safeexec v1.0.1 // indirect
15+
github.com/davecgh/go-spew v1.1.1 // indirect
1416
github.com/google/go-querystring v1.1.0 // indirect
17+
github.com/pmezard/go-difflib v1.0.0 // indirect
1518
golang.org/x/net v0.23.0 // indirect
1619
gopkg.in/yaml.v3 v3.0.1 // indirect
1720
)

libs/go.sum

+5-4
Original file line numberDiff line numberDiff line change
@@ -619,10 +619,10 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
619619
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
620620
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
621621
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
622-
github.com/cli/go-gh/v2 v2.9.0 h1:D3lTjEneMYl54M+WjZ+kRPrR5CEJ5BHS05isBPOV3LI=
623-
github.com/cli/go-gh/v2 v2.9.0/go.mod h1:MeRoKzXff3ygHu7zP+NVTT+imcHW6p3tpuxHAzRM2xE=
624-
github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI=
625-
github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=
622+
github.com/cli/go-gh/v2 v2.11.1 h1:amAyfqMWQTBdue8iTmDUegGZK7c8kk6WCxD9l/wLtGI=
623+
github.com/cli/go-gh/v2 v2.11.1/go.mod h1:MeRoKzXff3ygHu7zP+NVTT+imcHW6p3tpuxHAzRM2xE=
624+
github.com/cli/safeexec v1.0.1 h1:e/C79PbXF4yYTN/wauC4tviMxEV13BwljGj0N9j+N00=
625+
github.com/cli/safeexec v1.0.1/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=
626626
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
627627
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
628628
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
@@ -997,6 +997,7 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
997997
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
998998
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
999999
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
1000+
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
10001001
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
10011002
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
10021003
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=

tools/amplify-preview/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build/*

tools/amplify-preview/Makefile

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
TOOL_NAME = amplify-preview
2+
3+
include ../repo-release-tooling/tooling.mk

tools/amplify-preview/README.md

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# amplify-preview
2+
3+
4+
This gha-tool is basically re-implements what [AWS Amplify's GitHub integration should be doing](https://docs.aws.amazon.com/amplify/latest/userguide/pr-previews.html),
5+
however because of following limitations, we can't really use it for some of the repos:
6+
- [No way to filter for which PRs to generate preview deployments](https://github.com/aws-amplify/amplify-hosting/issues/3960)
7+
- [Hard limit of 50 preview branches per amplify app][https://docs.aws.amazon.com/amplify/latest/userguide/quotas-chapter.html]
8+
- [No way to create PR preview programmatically](https://github.com/aws-amplify/amplify-hosting/issues/3963)
9+
10+
This action accepts of AWS Amplify App IDs, checks if current git branch is connected to the apps and posts deployment status and PR preview in PR comments.
11+
12+
If `--create-branches` is enabled, then it will also connect git branch to one of the AWS Amplify apps (where hard limit of 50 branches hasn't been reached yet) and kick of new build.
13+
If `--wait` is enabled, then it will also wait for deployment to be completed and fail the GHA run if deployment had failed.
14+
15+
## Usage
16+
17+
```shell
18+
usage: amplify-preview --amplify-app-ids=AMPLIFY-APP-IDS --git-branch-name=GIT-BRANCH-NAME [<flags>]
19+
20+
Flags:
21+
--[no-]help Show context-sensitive help (also try --help-long and --help-man).
22+
--amplify-app-ids=AMPLIFY-APP-IDS ...
23+
List of Amplify App IDs ($AMPLIFY_APP_IDS)
24+
--git-branch-name=GIT-BRANCH-NAME
25+
Git branch name ($GIT_BRANCH_NAME)
26+
--[no-]create-branches Defines whether Amplify branches should be created if missing, or just lookup existing ones ($CREATE_BRANCHES)
27+
--[no-]wait Wait for pending/running job to complete ($WAIT)
28+
```
29+
30+
Example GHA workflow:
31+
32+
```yaml
33+
name: Amplify Preview
34+
on:
35+
pull_request:
36+
paths:
37+
- 'docs/**'
38+
workflow_dispatch:
39+
40+
permissions:
41+
# Permissions to write PR comment
42+
pull-requests: write
43+
id-token: write
44+
45+
jobs:
46+
amplify-preview:
47+
name: Get and post Amplify preview URL
48+
runs-on: ubuntu-22.04-2core-arm64
49+
environment: docs-amplify
50+
steps:
51+
- name: Checkout shared-workflow
52+
uses: actions/checkout@v4
53+
with:
54+
repository: gravitational/shared-workflows
55+
sparse-checkout: |
56+
tools
57+
58+
- name: Configure AWS credentials
59+
uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4
60+
with:
61+
aws-region: us-west-2
62+
role-to-assume: ${{ vars.IAM_ROLE }}
63+
64+
- name: Check Amplify job status test
65+
uses: ./tools/amplify-preview
66+
with:
67+
app_ids: ${{ vars.AMPLIFY_APP_IDS }}
68+
create_branches: "true"
69+
github_token: ${{ secrets.GITHUB_TOKEN }}
70+
wait: "true"
71+
```
72+
73+
## AWS Permissions
74+
75+
For this action to work, AWS role with following IAM permissions is required:
76+
```json
77+
{
78+
"Statement": [
79+
{
80+
"Action": [
81+
"amplify:CreateBranch",
82+
"amplify:GetBranch",
83+
"amplify:ListJobs"
84+
"amplify:StartJob",
85+
],
86+
"Effect": "Allow",
87+
"Resource": [
88+
"arn:aws:amplify:<region>:<account_id>:apps/<app_id>/branches/*"
89+
]
90+
}
91+
],
92+
"Version": "2012-10-17"
93+
}
94+
```
95+
96+
Where `amplify:CreateBranch` and `amplify:StartJob` are needed only when `--create-branches` is enabled.

tools/amplify-preview/action.yaml

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: Amplify Preview
2+
description: Prepare Amplify Preview URL and post it in PR comments
3+
inputs:
4+
app_ids:
5+
description: "Comma separated list of Amplify App IDs"
6+
required: true
7+
create_branches:
8+
description: 'Create preview branches using this actions instead of "auto-build" feature on AWS Amplify'
9+
required: false
10+
default: "false"
11+
github_token:
12+
required: true
13+
description: "Github token with permissions to read/write comments in pull request"
14+
wait:
15+
default: "false"
16+
description: "If Amplify deployment is pending/running state wait for it's completion"
17+
runs:
18+
using: composite
19+
steps:
20+
- name: Extract branch name
21+
shell: bash
22+
run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT
23+
id: extract_branch
24+
25+
- uses: actions/setup-go@v5
26+
with:
27+
go-version-file: tools/amplify-preview/go.mod
28+
cache-dependency-path: tools/amplify-preview/go.sum
29+
30+
- name: Amplify Preview
31+
env:
32+
AMPLIFY_APP_IDS: ${{ inputs.app_ids }}
33+
GIT_BRANCH_NAME: ${{ steps.extract_branch.outputs.branch }}
34+
CREATE_BRANCHES: ${{ inputs.create_branches }}
35+
GITHUB_TOKEN: ${{ inputs.github_token }}
36+
WAIT: ${{ inputs.wait }}
37+
shell: bash
38+
run: |
39+
pushd ./tools/amplify-preview/; go run ./; popd

0 commit comments

Comments
 (0)