Skip to content

Commit d2d7f46

Browse files
authored
remove always_on support in v6 (#123)
* remove always_on branch deployments and docs references * chore: sync wiki docs submodule pointer * chore(dist): update bundled pullpreview binary * chore: remove always_on references from workflows/docs * chore(dist): update bundled pullpreview binary * chore: restore historical changelog entry * chore(dist): update bundled pullpreview binary * docs: direct changelog and AGENTS guidance to releases * chore(dist): update bundled pullpreview binary * test: speed up hetzner SSH retry loops in slow lifecycle tests * chore(dist): update bundled pullpreview binary * test: import time for hetzner retry delay tests * chore(dist): update bundled pullpreview binary * docs: require make test before push in AGENTS * chore(dist): update bundled pullpreview binary
1 parent b0421fb commit d2d7f46

13 files changed

Lines changed: 42 additions & 64 deletions

File tree

.github/workflows/pullpreview.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ jobs:
3030
uses: "./"
3131
with:
3232
admins: "@collaborators/push"
33-
always_on: master,v6
3433
app_path: ./examples/workflow-smoke
3534
instance_type: micro
3635
max_domain_length: 30
@@ -100,7 +99,6 @@ jobs:
10099
uses: "./"
101100
with:
102101
admins: "@collaborators/push"
103-
always_on: master,v6
104102
app_path: ./examples/workflow-smoke
105103
instance_type: micro
106104
max_domain_length: 30
@@ -154,7 +152,6 @@ jobs:
154152
uses: "./"
155153
with:
156154
admins: "@collaborators/push"
157-
always_on: master,v6
158155
app_path: ./examples/workflow-smoke
159156
provider: hetzner
160157
region: ash

AGENTS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ This repository ships a GitHub Action implemented in Go.
1515
- `make dist`
1616
- `mise exec -- go test ./...`
1717
- `mise exec -- go run ./cmd/pullpreview up examples/example-app`
18+
- Run `make test` before any push.
19+
- Changelog updates are maintained in GitHub Releases; `CHANGELOG.md` does not need to be amended for routine release notes.
1820
- Always run `make dist` before pushing source changes so the bundled CLI binary stays in sync.
1921
- `make dist` builds the prebuilt Linux binary under `dist/` and auto-commits only that directory via the repo’s `dist-commit` target.
2022
- Dist workflow:
@@ -55,7 +57,6 @@ Supported commands:
5557

5658
## GitHub sync behavior (`github-sync`)
5759
- Handles PR labeled/opened/reopened/synchronize/unlabeled/closed events.
58-
- Handles push events for `always_on` branches.
5960
- Handles scheduled cleanup of dangling labeled preview instances.
6061
- Updates marker-based PR status comments.
6162
- For `admins: "@collaborators/push"`:

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
The changelog is published on the project releases page and is the canonical source for release notes: https://github.com/pullpreview/action/releases
2+
13
## v6.0.0
24

35
### Breaking changes

README.md

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
# <img width="25" height="25" alt="pullpreview" src="https://github.com/user-attachments/assets/3aeb0f94-cac5-44b2-9f8e-abdb12be9cfe" /> PullPreview
22

3-
A GitHub Action that starts live environments for your pull requests and branches.
3+
A GitHub Action that starts live environments for your pull requests.
44

55
[![pullpreview](https://github.com/pullpreview/action/actions/workflows/pullpreview.yml/badge.svg)](https://github.com/pullpreview/action/actions/workflows/pullpreview.yml)
66
<a href="https://news.ycombinator.com/item?id=23221471"><img src="https://img.shields.io/badge/Hacker%20News-83-%23FF6600" alt="Hacker News"></a>
77

88
## Spin environments in one click
99

1010
Once installed in your repository, this action is triggered any time a change
11-
is made to Pull Requests labelled with the `pullpreview` label, or one of the
12-
_always-on_ branches.
11+
is made to Pull Requests labelled with the `pullpreview` label.
1312

1413
When triggered, it will:
1514

1615
1. Check out the repository code
1716
2. Provision a preview instance (Lightsail by default, or Hetzner with `provider: hetzner`), with docker and docker-compose set up
18-
3. Continuously deploy the specified pull requests and branches, using your docker-compose file(s)
17+
3. Continuously deploy the specified pull requests using your docker-compose file(s)
1918
4. Report the preview instance URL in the GitHub UI
2019

2120
It is designed to be the **no-nonsense, cheap, and secure** alternative to
@@ -61,9 +60,7 @@ Preview environments that:
6160
docker-compose, it can be deployed to preview environments with PullPreview.
6261

6362
- can be **started and destroyed easily**: You can manage preview environments
64-
by adding or removing the `pullpreview` label on your Pull Requests. You can
65-
set specific branches as always on, for instance to continuously deploy your
66-
master branch.
63+
by adding or removing the `pullpreview` label on your Pull Requests.
6764

6865
- are **cheap too run**: Preview environments are launched on AWS Lightsail
6966
instances, which are both very cheap (10USD per month, proratized to the
@@ -110,7 +107,6 @@ All supported `with:` inputs from `action.yml`:
110107
| Input | Default | Description |
111108
| --- | --- | --- |
112109
| `app_path` | `.` | Path to your application containing Docker Compose files (relative to `${{ github.workspace }}`). |
113-
| `always_on` | `""` | Comma-separated branch names that should always be deployed. |
114110
| `dns` | `my.preview.run` | DNS suffix used for generated preview hostnames. Built-in alternatives: `rev1.click` through `rev9.click` (see note below). |
115111
| `max_domain_length` | `62` | Maximum generated FQDN length (cannot exceed 62 for Let's Encrypt). |
116112
| `label` | `pullpreview` | Label that triggers preview deployments. |
@@ -150,7 +146,7 @@ ssh-keygen -t rsa -b 3072 -m PEM -N "" -f hetzner_ca_key
150146

151147
## Example
152148

153-
Workflow file with the `master` branch always on:
149+
Workflow file for pullpreview-driven deployments:
154150

155151
```yaml
156152
# .github/workflows/pullpreview.yml
@@ -176,8 +172,6 @@ jobs:
176172
with:
177173
# Those GitHub users will have SSH access to the servers
178174
admins: crohr,other-github-user
179-
# A preview environment will always exist for the main branch
180-
always_on: main
181175
# Use the cidrs option to restrict access to the live environments to specific IP ranges
182176
cidrs: "0.0.0.0/0"
183177
# PullPreview will use those 2 files when running docker-compose up
@@ -220,7 +214,6 @@ jobs:
220214
- uses: pullpreview/action@v6
221215
with:
222216
admins: "@collaborators/push"
223-
always_on: master
224217
app_path: ./examples/workflow-smoke
225218
provider: hetzner
226219
# optional Hetzner runtime options

action.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ inputs:
99
description: "The path to your application containing a docker-compose file"
1010
default: "."
1111
required: false
12-
always_on:
13-
description: "List of always-on branches, irrespective of whether they correspond to an open Pull Request, comma-separated"
14-
default: ""
1512
dns:
1613
description: "Which DNS suffix to use"
1714
default: "my.preview.run"
@@ -164,7 +161,6 @@ runs:
164161
--label "${{ inputs.label }}" \
165162
--ports "${{ inputs.ports }}" \
166163
--default-port "${{ inputs.default_port }}" \
167-
--always-on "${{ inputs.always_on }}" \
168164
--instance-type "${{ inputs.instance_type }}" \
169165
--region "${{ inputs.region }}" \
170166
--image "${{ inputs.image }}" \

cmd/pullpreview/main.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,6 @@ func runGithubSync(ctx context.Context, args []string, logger *pullpreview.Logge
115115
verbose := fs.Bool("verbose", false, "Enable verbose mode")
116116
label := fs.String("label", "pullpreview", "Label to use for triggering preview deployments")
117117
deploymentVariant := fs.String("deployment-variant", "", "Deployment variant (4 chars max)")
118-
alwaysOn := fs.String("always-on", "", "List of branches to always deploy")
119118
ttl := fs.String("ttl", "infinite", "Maximum time to live for deployments (e.g. 10h, 5d, infinite)")
120119
commonFlags := registerCommonFlags(fs)
121120
leadingPath, parseArgs := splitLeadingPositional(args)
@@ -136,7 +135,6 @@ func runGithubSync(ctx context.Context, args []string, logger *pullpreview.Logge
136135
opts := pullpreview.GithubSyncOptions{
137136
AppPath: appPath,
138137
Label: *label,
139-
AlwaysOn: splitCommaList(*alwaysOn),
140138
DeploymentVariant: *deploymentVariant,
141139
TTL: *ttl,
142140
Context: ctx,

dist/pullpreview-linux-amd64

-1000 Bytes
Binary file not shown.

internal/providers/hetzner/hetzner.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,8 @@ type Provider struct {
229229
caSigner ssh.Signer
230230
caPublicKey string
231231
sshKeysCacheDir string
232+
sshRetryCount int
233+
sshRetryDelay time.Duration
232234
logger *pullpreview.Logger
233235
}
234236

@@ -263,6 +265,8 @@ func newProviderWithContext(ctx context.Context, cfg Config, logger *pullpreview
263265
caSigner: caSigner,
264266
caPublicKey: caPublicKey,
265267
sshKeysCacheDir: cfg.SSHKeysCacheDir,
268+
sshRetryCount: defaultHetznerSSHRetries,
269+
sshRetryDelay: defaultHetznerSSHInterval,
266270
logger: logger,
267271
}, nil
268272
}
@@ -511,7 +515,15 @@ func (p *Provider) createServer(name string, opts pullpreview.LaunchOptions) (pu
511515

512516
func (p *Provider) validateSSHAccessWithRetry(server *hcloud.Server, privateKey, certKey string, attempts int) error {
513517
if attempts <= 0 {
514-
attempts = 1
518+
if p.sshRetryCount > 0 {
519+
attempts = p.sshRetryCount
520+
} else {
521+
attempts = 1
522+
}
523+
}
524+
delay := p.sshRetryDelay
525+
if delay <= 0 {
526+
delay = defaultHetznerSSHInterval
515527
}
516528
var lastErr error
517529
for i := 0; i < attempts; i++ {
@@ -524,7 +536,7 @@ func (p *Provider) validateSSHAccessWithRetry(server *hcloud.Server, privateKey,
524536
if p.logger != nil {
525537
p.logger.Warnf("SSH access validation failed for %q (attempt %d/%d): %v", strings.TrimSpace(server.Name), i+1, attempts, lastErr)
526538
}
527-
time.Sleep(defaultHetznerSSHInterval)
539+
time.Sleep(delay)
528540
}
529541
}
530542
return fmt.Errorf("ssh access validation failed for %q after %d attempts: %w", strings.TrimSpace(server.Name), attempts, lastErr)

internal/providers/hetzner/hetzner_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"path/filepath"
99
"strings"
1010
"testing"
11+
"time"
1112

1213
"github.com/hetznercloud/hcloud-go/v2/hcloud"
1314
"github.com/pullpreview/action/internal/pullpreview"
@@ -502,6 +503,8 @@ func TestHetznerLaunchLifecycleRecreateWhenCacheMissing(t *testing.T) {
502503
}
503504
return []byte("ok"), nil
504505
}
506+
provider.sshRetryCount = 1
507+
provider.sshRetryDelay = 1 * time.Millisecond
505508

506509
_, err := provider.Launch(instance, pullpreview.LaunchOptions{})
507510
if err != nil {
@@ -579,6 +582,8 @@ func TestHetznerCreateLifecycleRecreateWhenSSHPrecheckFails(t *testing.T) {
579582
runSSHCommand = func(context.Context, string, string, string, string) ([]byte, error) {
580583
return nil, fmt.Errorf("ssh unavailable")
581584
}
585+
provider.sshRetryCount = 1
586+
provider.sshRetryDelay = 1 * time.Millisecond
582587

583588
_, err := provider.Launch(instance, pullpreview.LaunchOptions{})
584589
if err == nil {

internal/pullpreview/github_sync.go

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -196,12 +196,6 @@ func clearDanglingDeployments(repo string, opts GithubSyncOptions, provider Prov
196196
logger.Warnf("[clear_dangling_deployments] Unable to remove %s label for PR#%d: %v", opts.Label, number, err)
197197
}
198198
}
199-
alwaysOn := map[string]struct{}{}
200-
alwaysOnNormalized := map[string]struct{}{}
201-
for _, branch := range uniqueStrings(opts.AlwaysOn) {
202-
alwaysOn[branch] = struct{}{}
203-
alwaysOnNormalized[NormalizeName(branch)] = struct{}{}
204-
}
205199
activeInstanceNames := []string{}
206200
removedInstanceNames := []string{}
207201
for _, inst := range instances {
@@ -223,12 +217,8 @@ func clearDanglingDeployments(repo string, opts GithubSyncOptions, provider Prov
223217
detail = fmt.Sprintf("PR#%s not active/labeled", ref.PRNumber)
224218
}
225219
} else {
226-
_, exact := alwaysOn[ref.Branch]
227-
_, normalized := alwaysOnNormalized[ref.BranchNormalized]
228-
if !exact && !normalized {
229-
dangling = true
230-
detail = fmt.Sprintf("branch %q not always_on", ref.Branch)
231-
}
220+
dangling = true
221+
detail = fmt.Sprintf("branch %q not linked to an active PR", ref.Branch)
232222
}
233223
if !dangling {
234224
activeInstanceNames = append(activeInstanceNames, inst.Name)
@@ -482,7 +472,7 @@ func (g *GithubSync) Sync() error {
482472
}
483473
_ = g.updateGitHubStatus(statusDestroyed, "")
484474
g.writeStepSummary(statusDestroyed, action, "", nil)
485-
case actionPRUp, actionPRPush, actionBranchPush:
475+
case actionPRUp, actionPRPush:
486476
_ = g.updateGitHubStatus(statusDeploying, "")
487477
instance := g.buildInstance()
488478
var upInstance *Instance
@@ -511,7 +501,6 @@ const (
511501
actionBranchDown actionType = "branch_down"
512502
actionPRUp actionType = "pr_up"
513503
actionPRPush actionType = "pr_push"
514-
actionBranchPush actionType = "branch_push"
515504
)
516505

517506
type deploymentStatus string
@@ -526,10 +515,6 @@ const (
526515

527516
func (g *GithubSync) guessAction() actionType {
528517
if g.prNumber() == 0 {
529-
branch := strings.TrimPrefix(g.ref(), "refs/heads/")
530-
if containsString(g.opts.AlwaysOn, branch) {
531-
return actionBranchPush
532-
}
533518
return actionBranchDown
534519
}
535520

@@ -1151,12 +1136,3 @@ func instanceToCommon(inst *Instance) CommonOptions {
11511136
PreScript: inst.PreScript,
11521137
}
11531138
}
1154-
1155-
func containsString(list []string, value string) bool {
1156-
for _, v := range list {
1157-
if v == value {
1158-
return true
1159-
}
1160-
}
1161-
return false
1162-
}

0 commit comments

Comments
 (0)