Skip to content

Commit 827bd1b

Browse files
authored
Merge pull request #646 from github/enterprise-3.9-backport-579-hao/integration-actions
Backport 579 for 3.9: Add integration tests to backup-utils-private
2 parents 9be46ed + 55e9f29 commit 827bd1b

File tree

5 files changed

+270
-0
lines changed

5 files changed

+270
-0
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: 'Trigger a CI Job on Janky'
2+
description: 'Action to trigger and poll a Janky CI job'
3+
inputs:
4+
janky-token:
5+
description: 'Token for making request to Janky'
6+
required: true
7+
job-name:
8+
description: 'The name of the job to run'
9+
required: true
10+
branch-name:
11+
description: 'The name of the branch to use'
12+
required: true
13+
force:
14+
description: 'Force the job to run even if it is already passed'
15+
required: false
16+
envVars:
17+
description: 'Comma separated list of key value pairs to pass to Janky - ex: key1=value1,key2=value2,key3=value3'
18+
required: false
19+
runs:
20+
using: 'composite'
21+
steps:
22+
- uses: actions/setup-go@a3d889c34c5d4e071b33595c5fe8edfcaaad8260
23+
with:
24+
go-version: '1.21'
25+
- run: |
26+
go run main.go \
27+
-token ${{ inputs.janky-token }} \
28+
-job ${{ inputs.job-name }} \
29+
-branch ${{ inputs.branch-name }} \
30+
-force ${{ inputs.force }} \
31+
-envVars ${{ inputs.envVars }}
32+
shell: bash
33+
working-directory: .github/actions/proxy-janky-build
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module github.com/github/enterprise2/actions/proxy-janky-build
2+
3+
go 1.21
4+
5+
require github.com/hashicorp/go-retryablehttp v0.7.2
6+
7+
require github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2+
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
3+
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
4+
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
5+
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
6+
github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0=
7+
github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
8+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
9+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"encoding/base64"
6+
"encoding/json"
7+
"flag"
8+
"fmt"
9+
"io"
10+
"log"
11+
"net/http"
12+
"regexp"
13+
"strings"
14+
"time"
15+
16+
"github.com/hashicorp/go-retryablehttp"
17+
)
18+
19+
// Define our Janky Response Structs
20+
type JankyBuildStruct struct {
21+
Result string
22+
Url string
23+
}
24+
type JankyStatusStruct struct {
25+
Id string
26+
Green bool
27+
Completed bool
28+
StartedAt string
29+
CompletedAt string
30+
Sha string
31+
BuildableName string
32+
}
33+
34+
const (
35+
pollWaitTime = 10 * time.Second
36+
jankyPollTimeout = 5 * time.Hour
37+
jankyHttpRetryMax = 5
38+
jankyUrl = "https://janky.githubapp.com"
39+
)
40+
41+
func main() {
42+
// Parse command-line arguments
43+
job := flag.String("job", "", "Name of the Janky job")
44+
token := flag.String("token", "", "Name of the Janky token")
45+
branch := flag.String("branch", "", "Name of the Git branch")
46+
force := flag.String("force", "false", "Force a build even if one is already passed")
47+
envVars := flag.String("envVars", "", "Comma separated list of key value pairs to pass to Janky - ex: key1=value1,key2=value2,key3=value3")
48+
flag.Parse()
49+
50+
// Validate command-line arguments
51+
if *job == "" || *token == "" || *branch == "" {
52+
log.Fatal("job, token and branch flags must be specified")
53+
}
54+
55+
// Set up the token + request payload
56+
authToken := base64.StdEncoding.EncodeToString([]byte(":" + *token))
57+
type buildRequestObject struct {
58+
BuildableName string `json:"buildable_name"`
59+
BranchName string `json:"branch_name"`
60+
Force string `json:"force"`
61+
EnvVars map[string]string `json:"env_vars"`
62+
}
63+
64+
requestBody := buildRequestObject{
65+
BuildableName: *job,
66+
BranchName: *branch,
67+
Force: *force,
68+
}
69+
70+
// Parse the envVars flag into a map and add to the request payload
71+
fmt.Println("Environment Variables:")
72+
fmt.Println(*envVars)
73+
if *envVars != "" {
74+
envVarsMap := make(map[string]string)
75+
for _, envVar := range strings.Split(*envVars, ",") {
76+
envVarSplit := strings.Split(envVar, "=")
77+
envVarsMap[envVarSplit[0]] = envVarSplit[1]
78+
}
79+
requestBody.EnvVars = envVarsMap
80+
}
81+
82+
payloadBytes, err := json.Marshal(requestBody)
83+
if err != nil {
84+
log.Fatal("Failed to marshal the JSON payload!\n" + err.Error())
85+
}
86+
87+
// Send build request to Janky
88+
buildRequest, err := http.NewRequest("POST", jankyUrl+"/api/builds", bytes.NewBuffer(payloadBytes))
89+
if err != nil {
90+
log.Fatal("Failed to create build request!\n" + err.Error())
91+
}
92+
buildRequest.Header.Set("Content-Type", "application/json")
93+
buildRequest.Header.Set("Authorization", "Basic "+authToken)
94+
retryClient := retryablehttp.NewClient() //nolint:all
95+
retryClient.RetryMax = jankyHttpRetryMax
96+
retryClient.Logger = nil // disable debug logging
97+
client := retryClient.StandardClient() // uses *http.Client
98+
resp, err := client.Do(buildRequest)
99+
if err != nil {
100+
log.Fatal("Failed to send build request!\n" + err.Error())
101+
}
102+
defer resp.Body.Close()
103+
body, err := io.ReadAll(resp.Body)
104+
if err != nil {
105+
log.Fatal("Error reading build response!\n" + err.Error())
106+
}
107+
108+
// Check if the build was triggered successfully
109+
if resp.StatusCode == 404 {
110+
log.Fatal("Failed to trigger build! Either " + *job + " is not the name of a Janky job or " + *branch + " is not a branch for the repository that job belongs to.")
111+
}
112+
if resp.StatusCode != 201 {
113+
log.Fatal("Failed to trigger build! Got exception: " + string(body))
114+
}
115+
116+
// Parse the build request response
117+
var buildResponse JankyBuildStruct
118+
json.Unmarshal(body, &buildResponse)
119+
log.Println("Succesfully triggered janky!\n" + buildResponse.Result)
120+
121+
// Parse the request response for the buildId
122+
r, err := regexp.Compile("/[0-9]+/")
123+
if err != nil {
124+
log.Fatal("Failed to trigger build!\n" + err.Error())
125+
}
126+
buildId := strings.Trim(r.FindString(buildResponse.Result), "/")
127+
128+
// Setup our second HTTP client for reuse in during status polling
129+
jankyStatusUrl := jankyUrl + "/api/" + buildId + "/status"
130+
statusRequest, err := http.NewRequest("GET", jankyStatusUrl, nil)
131+
if err != nil {
132+
log.Fatal("Failed to create status request!\n" + err.Error())
133+
}
134+
statusRequest.Header.Set("Content-Type", "application/json")
135+
statusRequest.Header.Set("Authorization", "Basic "+authToken)
136+
retryClient2 := retryablehttp.NewClient() //nolint:all
137+
retryClient2.RetryMax = jankyHttpRetryMax
138+
retryClient2.Logger = nil // disable debug logging
139+
client2 := retryClient2.StandardClient() // uses *http.Client
140+
141+
// Wait for a completed status from Janky or break the loop after a certain amount of time
142+
timeout := time.NewTimer(jankyPollTimeout)
143+
poll := time.NewTicker(pollWaitTime)
144+
145+
jobLoop:
146+
for {
147+
select {
148+
case <-timeout.C:
149+
log.Fatal("Failed to poll for build status after " + jankyPollTimeout.String() + "hours")
150+
case <-poll.C:
151+
// Send build status request to Janky
152+
statusResponse, err := client2.Do(statusRequest)
153+
if err != nil {
154+
log.Fatal("Failed to send status request!\n" + err.Error())
155+
}
156+
defer statusResponse.Body.Close()
157+
statusBody, err := io.ReadAll(statusResponse.Body)
158+
if err != nil {
159+
log.Fatal("Error reading status response!\n" + err.Error())
160+
}
161+
162+
// Parse the status response for a green completed build
163+
var jankyStatusResponse JankyStatusStruct
164+
json.Unmarshal(statusBody, &jankyStatusResponse)
165+
//fmt.Println("Janky Status Response:")
166+
//fmt.Println(string(statusBody))
167+
if jankyStatusResponse.Completed && jankyStatusResponse.Green {
168+
log.Println("Janky build Succeeded!")
169+
break jobLoop
170+
}
171+
if jankyStatusResponse.Completed && !jankyStatusResponse.Green {
172+
log.Fatal("Build failed, see Janky for more info: " + buildResponse.Url)
173+
}
174+
175+
// wait for a bit and try again
176+
log.Println("Build still in progress, will poll for status again in [" + pollWaitTime.String() + "]")
177+
continue
178+
}
179+
}
180+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: Run Integration Tests
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize, reopened, ready_for_review]
6+
branches: ['master', 'enterprise-[0-9]*.[0-9]*-release', 'enterprise-[0-9]*.[0-9]*.[0-9]*-release']
7+
workflow_dispatch:
8+
inputs:
9+
target-branch:
10+
description: 'Branch that would be merged into'
11+
required: true
12+
source-branch:
13+
description: 'Branch that would be merged'
14+
required: true
15+
16+
# Get target and source branch from different variables depending on how it was triggered
17+
env:
18+
TARGET_BRANCH: '${{ github.event.inputs.target-branch }}${{ github.base_ref || github.ref_name }}'
19+
SOURCE_BRANCH: '${{ github.event.inputs.source-branch }}${{ github.head_ref || github.ref_name }}'
20+
21+
jobs:
22+
integration-tests:
23+
runs-on: ubuntu-latest
24+
strategy:
25+
matrix:
26+
jankyJobName:
27+
- enterprise2-backup-utils-binary-backup
28+
- enterprise2-backup-utils-migration
29+
steps:
30+
- uses: actions/checkout@v3
31+
with:
32+
fetch-depth: 1
33+
- name: Queue ${{ matrix.jankyJobName }} build
34+
uses: ./.github/actions/proxy-janky-build
35+
id: proxy-janky-build
36+
with:
37+
janky-token: '${{ secrets.API_AUTH_TOKEN }}'
38+
job-name: '${{ matrix.jankyJobName }}'
39+
branch-name: '${{ env.TARGET_BRANCH }}'
40+
force : "true"
41+
envVars: "JANKY_ENV_BACKUP_UTILS_BRANCH=${{ env.SOURCE_BRANCH }}"

0 commit comments

Comments
 (0)