Skip to content

Commit a379d97

Browse files
authored
Create beta build (#156)
* Create beta build for separate publishing to webstore. * Add workflow to publish beta release * Validate that prod and beta manifest versions match * Update Go dependencies (#155)
1 parent c603853 commit a379d97

File tree

8 files changed

+240
-97
lines changed

8 files changed

+240
-97
lines changed

.github/workflows/release-beta.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Release Beta
2+
3+
on:
4+
# New release tagged.
5+
push:
6+
tags:
7+
- 'v[0-9]+.[0-9]+.*'
8+
# Manual trigger.
9+
workflow_dispatch:
10+
11+
jobs:
12+
release-beta:
13+
if: startsWith(github.ref, 'refs/tags/v')
14+
runs-on: ubuntu-latest
15+
permissions:
16+
# This is broad, but unfortunately it is required for creating
17+
# releases.
18+
contents: write
19+
steps:
20+
- uses: actions/checkout@v3
21+
- uses: bazelbuild/setup-bazelisk@v2
22+
- name: Check Manifest
23+
run: |
24+
MANIFEST_VERSION=$(cat manifest-beta.json | python3 -c "import sys, json; print(json.load(sys.stdin)['version'])")
25+
TAG_VERSION=${{ github.ref_name }}
26+
test "v${MANIFEST_VERSION}" = "${TAG_VERSION}"
27+
- run: bazel build ...
28+
- run: bazel test --test_output=errors ...
29+
- name: Create Release
30+
uses: softprops/action-gh-release@v1
31+
with:
32+
generate_release_notes: true
33+
fail_on_unmatched_files: true
34+
# bazel-bin symlink may not exist
35+
files: |
36+
bazel-out/k8-fastbuild/bin/chrome-ssh-agent-beta.zip
37+
- name: Publish to Webstore
38+
uses: mnao305/[email protected]
39+
with:
40+
file-path: bazel-out/k8-fastbuild/bin/chrome-ssh-agent-beta.zip
41+
extension-id: onabphcdiffmanfdhkihllckikaljmhh
42+
client-id: ${{ secrets.WEBSTORE_CLIENT_ID }}
43+
client-secret: ${{ secrets.WEBSTORE_CLIENT_SECRET }}
44+
refresh-token: ${{ secrets.WEBSTORE_REFRESH_TOKEN }}

.github/workflows/release.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
name: Release
22

33
on:
4-
# New release tagged.
5-
push:
6-
tags:
7-
- 'v[0-9]+.[0-9]+.*'
84
# Manual trigger.
95
workflow_dispatch:
106

BUILD

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ load("@bazel_gazelle//:def.bzl", "gazelle")
22
load("@npm//:defs.bzl", "npm_link_all_packages")
33
load("@io_bazel_rules_go//go:def.bzl", "TOOLS_NOGO", "nogo")
44
load("@rules_pkg//:pkg.bzl", "pkg_zip")
5+
load("@rules_pkg//pkg:mappings.bzl", "pkg_filegroup", "pkg_files")
56

67
# gazelle:prefix github.com/google/chrome-ssh-agent
78

@@ -54,17 +55,59 @@ exports_files([
5455

5556
npm_link_all_packages(name = "node_modules")
5657

57-
pkg_zip(
58-
name = "chrome-ssh-agent",
58+
pkg_files(
59+
name = "pkg_doc",
5960
srcs = [
6061
":CONTRIBUTING.md",
6162
":LICENSE",
6263
":README.md",
63-
":manifest.json",
64+
],
65+
)
66+
67+
68+
pkg_filegroup(
69+
name = "pkg_common",
70+
srcs = [
71+
":pkg_doc",
6472
"//go/background:pkg",
6573
"//go/options:pkg",
6674
"//html:pkg",
6775
"//img:pkg",
6876
],
77+
)
78+
79+
pkg_files(
80+
name = "pkg_manifest",
81+
srcs = [
82+
":manifest.json",
83+
]
84+
)
85+
86+
pkg_files(
87+
name = "pkg_manifest_beta",
88+
srcs = [
89+
":manifest-beta.json",
90+
],
91+
# Manifest must end up with well-known name.
92+
renames = {
93+
"manifest-beta.json": "manifest.json",
94+
}
95+
)
96+
97+
pkg_zip(
98+
name = "chrome-ssh-agent",
99+
srcs = [
100+
":pkg_common",
101+
":pkg_manifest",
102+
],
103+
visibility = ["//visibility:public"],
104+
)
105+
106+
pkg_zip(
107+
name = "chrome-ssh-agent-beta",
108+
srcs = [
109+
":pkg_common",
110+
":pkg_manifest_beta",
111+
],
69112
visibility = ["//visibility:public"],
70113
)

manifest-beta.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "SSH Agent for Google Chrome™ (BETA)",
3+
"version": "0.0.25",
4+
"description": "Provides an SSH Agent implementation for Chrome's Secure Shell extension",
5+
"manifest_version": 3,
6+
"icons": {
7+
"128": "img/icon128.png"
8+
},
9+
"background": {
10+
"service_worker": "html/background-bundle.js"
11+
},
12+
"options_page": "html/options.html",
13+
"options_ui": {
14+
"page": "html/options.html"
15+
},
16+
"action": {
17+
"default_popup": "html/options.html"
18+
},
19+
"content_security_policy": {
20+
"extension_pages" : "default-src 'self' 'wasm-unsafe-eval'"
21+
},
22+
"permissions": [
23+
"storage"
24+
],
25+
"externally_connectable": {
26+
"ids": [
27+
"pnhechapfaindjhompbnflcldabbghjo",
28+
"okddffdblfhhnmhodogpojmfkjmhinfp",
29+
"iodihamcpbpeioajjeobimgagajmlibd",
30+
"algkcnfjnajfhgimadimbjhmpaeohhln",
31+
"ooiklbnjmhbcgemelgfhaeaocllobloj",
32+
"hmgggebkhjjkiimkjlknpdgapncghehh"
33+
]
34+
},
35+
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsYE6b5ANIgvI8Twuv5CqOnXfphjSIPgD13vVNxSbNuUa9b8FAb1uvx1O9LIVhvQ7HOAUwn6Z+LO7Y6GBh05hTMSAHJ69UAoiRrsjdpkVcm+Je4Pkp7hv1R2IEOE+cEdWWfj2MlBqsc88CJ4DwP01gdGffbKPOn+KZzgSzB+6XvGTSqimO2snTUFChohKHQ3/6m6j5zc27LsahiPjiHiDVy0HNZkzp3Rh8F/XjeweYWMy1xUkV2oNoJjErk7qMGsJ0Vseqin+FlC7CHmnQxk16Sl9SUd5lmt+IfJ4ihlUzAnlKlYHAkfk6BbaiPN/9iQg2FfdDlxR6+s3Iv8EPmMukQIDAQAB"
36+
}

release/tag-release.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,16 @@ function die() {
2727

2828
# Read current version from manifest.
2929
readonly MANIFEST=${PWD}/manifest.json
30+
readonly MANIFEST_BETA=${PWD}/manifest-beta.json
3031
readonly VERSION=$(cat "${MANIFEST}" | python3 -c "import sys, json; print(json.load(sys.stdin)['version'])")
32+
readonly VERSION_BETA=$(cat "${MANIFEST_BETA}" | python3 -c "import sys, json; print(json.load(sys.stdin)['version'])")
3133
readonly TAG=v${VERSION}
3234

35+
# Ensure both manifests have the same version. This could happen if only one of
36+
# the manifests was updated.
37+
test "${VERSION}" = "${VERSION_BETA}" \
38+
|| die "Prod and Beta versions do not match; Prod is ${VERSION}, Beta is ${VERSION_BETA}"
39+
3340
# Ensure the tag doesn't already exist. This could happen if someone forgot to
3441
# update the version in manifest.json.
3542
test -z $(git tag | grep --line-regexp "${TAG}") \

test/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ go_test(
88
],
99
data = [
1010
"//:chrome-ssh-agent.zip",
11+
"//:chrome-ssh-agent-beta.zip",
1112
"@chromedriver",
1213
"@chromium",
1314
],

test/e2e.go

Lines changed: 105 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import (
1919
var (
2020
chromeDriverPath = testutil.MustRunfile("chromedriver.bin")
2121
chromePath = testutil.MustRunfile("chromium.bin")
22-
extensionPath = testutil.MustRunfile("chrome-ssh-agent.zip")
2322
)
2423

2524
func getElementText(wd selenium.WebDriver, id string) (string, error) {
@@ -97,92 +96,113 @@ func dumpLog(t *testing.T, name string, r io.Reader) {
9796
}
9897

9998
func TestWebApp(t *testing.T) {
100-
port, err := unusedPort()
101-
if err != nil {
102-
t.Fatalf("failed to identify unused port: %v", err)
103-
}
104-
105-
var selOut bytes.Buffer
106-
opts := []selenium.ServiceOption{
107-
selenium.Output(&selOut),
108-
}
109-
service, err := selenium.NewChromeDriverService(chromeDriverPath, port, opts...)
110-
if err != nil {
111-
defer dumpLog(t, "SeleniumOutput", &selOut) // Selenium failed to initialize; show debug info.
112-
t.Fatalf("failed to start Selenium service: %v", err)
113-
}
114-
defer service.Stop()
115-
116-
caps := selenium.Capabilities{}
117-
caps.AddLogging(logLevels)
118-
119-
t.Log("Preparing extension")
120-
extPath, extCleanup, err := testutil.UnzipTemp(extensionPath)
121-
if err != nil {
122-
t.Fatalf("Failed to unzip extension: %v", err)
123-
}
124-
defer extCleanup()
125-
126-
t.Log("Configuring extension in Chrome")
127-
chromeCaps := chrome.Capabilities{
128-
Path: chromePath,
129-
Args: []string{
130-
"--no-sandbox",
131-
// Specific headless mode that supports extensions. See:
132-
// https://bugs.chromium.org/p/chromium/issues/detail?id=706008#c36
133-
"--headless=chrome",
99+
testcases := []struct {
100+
name string
101+
extensionPath string
102+
extensionId string
103+
}{
104+
{
105+
name: "Prod Release",
106+
extensionPath: testutil.MustRunfile("chrome-ssh-agent.zip"),
107+
extensionId: "eechpbnaifiimgajnomdipfaamobdfha",
108+
},
109+
{
110+
name: "Beta Release",
111+
extensionPath: testutil.MustRunfile("chrome-ssh-agent-beta.zip"),
112+
extensionId: "onabphcdiffmanfdhkihllckikaljmhh",
134113
},
135-
}
136-
if err = chromeCaps.AddUnpackedExtension(extPath); err != nil {
137-
t.Fatalf("failed to add extension: %v", err)
138-
}
139-
caps.AddChrome(chromeCaps)
140-
141-
t.Log("Starting WebDriver")
142-
wd, err := selenium.NewRemote(caps, fmt.Sprintf("http://localhost:%d/wd/hub", port))
143-
if err != nil {
144-
defer dumpLog(t, "SeleniumOutput", &selOut) // Selenium failed to initialize; show debug info.
145-
t.Fatalf("Failed to start webdriver: %v", err)
146-
}
147-
defer wd.Quit()
148-
defer dumpSeleniumLogs(t, wd)
149-
150-
t.Log("Navigating to test page")
151-
path := makeExtensionUrl("html/options.html", "test")
152-
if err = wd.Get(path.String()); err != nil {
153-
t.Fatalf("Failed to navigate to %s: %v", path, err)
154-
}
155-
156-
t.Log("Waiting for navigation")
157-
if err = wd.WaitWithTimeout(currentURLIs(path.String()), 10*time.Second); err != nil {
158-
t.Fatalf("Failed to complete navigation to page: %v", err)
159-
}
160-
161-
t.Log("Waiting for results")
162-
if err = wd.WaitWithTimeout(elementExists("failureCount"), 30*time.Second); err != nil {
163-
t.Fatalf("failed to wait for failure count: %v", err)
164-
}
165-
if err = wd.WaitWithTimeout(elementExists("failures"), 30*time.Second); err != nil {
166-
t.Fatalf("failed to wait for failures: %v", err)
167-
}
168-
169-
t.Log("Extracting test results")
170-
countTxt, err := getElementText(wd, "failureCount")
171-
if err != nil {
172-
t.Fatalf("Failed to find failure count: %v", err)
173-
}
174-
175-
count, err := strconv.Atoi(countTxt)
176-
if err != nil {
177-
t.Fatalf("Failed to parse failure count '%s' as integer: %v", countTxt, err)
178-
}
179-
180-
failures, err := getElementText(wd, "failures")
181-
if err != nil {
182-
t.Fatalf("Failed to find failure details: %v", err)
183114
}
184115

185-
if count != 0 {
186-
t.Errorf("Reported Failures:\n%s", failures)
116+
for _, tc := range testcases {
117+
t.Run(tc.name, func(t *testing.T) {
118+
port, err := unusedPort()
119+
if err != nil {
120+
t.Fatalf("failed to identify unused port: %v", err)
121+
}
122+
123+
var selOut bytes.Buffer
124+
opts := []selenium.ServiceOption{
125+
selenium.Output(&selOut),
126+
}
127+
service, err := selenium.NewChromeDriverService(chromeDriverPath, port, opts...)
128+
if err != nil {
129+
defer dumpLog(t, "SeleniumOutput", &selOut) // Selenium failed to initialize; show debug info.
130+
t.Fatalf("failed to start Selenium service: %v", err)
131+
}
132+
defer service.Stop()
133+
134+
caps := selenium.Capabilities{}
135+
caps.AddLogging(logLevels)
136+
137+
t.Log("Preparing extension")
138+
extPath, extCleanup, err := testutil.UnzipTemp(tc.extensionPath)
139+
if err != nil {
140+
t.Fatalf("Failed to unzip extension: %v", err)
141+
}
142+
defer extCleanup()
143+
144+
t.Log("Configuring extension in Chrome")
145+
chromeCaps := chrome.Capabilities{
146+
Path: chromePath,
147+
Args: []string{
148+
"--no-sandbox",
149+
// Specific headless mode that supports extensions. See:
150+
// https://bugs.chromium.org/p/chromium/issues/detail?id=706008#c36
151+
"--headless=chrome",
152+
},
153+
}
154+
if err = chromeCaps.AddUnpackedExtension(extPath); err != nil {
155+
t.Fatalf("failed to add extension: %v", err)
156+
}
157+
caps.AddChrome(chromeCaps)
158+
159+
t.Log("Starting WebDriver")
160+
wd, err := selenium.NewRemote(caps, fmt.Sprintf("http://localhost:%d/wd/hub", port))
161+
if err != nil {
162+
defer dumpLog(t, "SeleniumOutput", &selOut) // Selenium failed to initialize; show debug info.
163+
t.Fatalf("Failed to start webdriver: %v", err)
164+
}
165+
defer wd.Quit()
166+
defer dumpSeleniumLogs(t, wd)
167+
168+
t.Log("Navigating to test page")
169+
path := makeExtensionUrl(tc.extensionId, "html/options.html", "test")
170+
if err = wd.Get(path.String()); err != nil {
171+
t.Fatalf("Failed to navigate to %s: %v", path, err)
172+
}
173+
174+
t.Log("Waiting for navigation")
175+
if err = wd.WaitWithTimeout(currentURLIs(path.String()), 10*time.Second); err != nil {
176+
t.Fatalf("Failed to complete navigation to page: %v", err)
177+
}
178+
179+
t.Log("Waiting for results")
180+
if err = wd.WaitWithTimeout(elementExists("failureCount"), 30*time.Second); err != nil {
181+
t.Fatalf("failed to wait for failure count: %v", err)
182+
}
183+
if err = wd.WaitWithTimeout(elementExists("failures"), 30*time.Second); err != nil {
184+
t.Fatalf("failed to wait for failures: %v", err)
185+
}
186+
187+
t.Log("Extracting test results")
188+
countTxt, err := getElementText(wd, "failureCount")
189+
if err != nil {
190+
t.Fatalf("Failed to find failure count: %v", err)
191+
}
192+
193+
count, err := strconv.Atoi(countTxt)
194+
if err != nil {
195+
t.Fatalf("Failed to parse failure count '%s' as integer: %v", countTxt, err)
196+
}
197+
198+
failures, err := getElementText(wd, "failures")
199+
if err != nil {
200+
t.Fatalf("Failed to find failure details: %v", err)
201+
}
202+
203+
if count != 0 {
204+
t.Errorf("Reported Failures:\n%s", failures)
205+
}
206+
})
187207
}
188208
}

0 commit comments

Comments
 (0)