From 77060d237faa9fc6d7a414572c4bb6d13b08a093 Mon Sep 17 00:00:00 2001 From: Cyril Rohr Date: Wed, 25 Feb 2026 17:42:06 +0100 Subject: [PATCH] Add templated_url support --- action.yml | 7 ++- cmd/pullpreview/main.go | 2 + internal/pullpreview/github_sync.go | 30 ++++++++++- internal/pullpreview/github_sync_test.go | 63 ++++++++++++++++++++++++ internal/pullpreview/types.go | 1 + 5 files changed, 100 insertions(+), 3 deletions(-) diff --git a/action.yml b/action.yml index 2d24ce7..0b097cb 100644 --- a/action.yml +++ b/action.yml @@ -81,6 +81,10 @@ inputs: description: "Path to a local bash script (relative to app_path) executed inline over SSH on the instance before docker compose" required: false default: "" + templated_url: + description: "Template for the preview URL displayed in PR comments and outputs (use {{ pullpreview_url }} as placeholder, e.g. {{ pullpreview_url }}/custom/path?key=value)" + required: false + default: "" ttl: description: "Maximum time to live for deployments (e.g. 10h, 5d, infinite)" required: false @@ -168,7 +172,8 @@ runs: --registries "${{ inputs.registries }}" \ --proxy-tls "${{ inputs.proxy_tls }}" \ --pre-script "${{ inputs.pre_script }}" \ - --ttl "${{ inputs.ttl }}" + --ttl "${{ inputs.ttl }}" \ + --templated-url "${{ inputs.templated_url }}" if grep -q '^url=' "${GITHUB_OUTPUT}"; then echo "live=true" >> "${GITHUB_OUTPUT}" diff --git a/cmd/pullpreview/main.go b/cmd/pullpreview/main.go index 34241c3..f35abbc 100644 --- a/cmd/pullpreview/main.go +++ b/cmd/pullpreview/main.go @@ -116,6 +116,7 @@ func runGithubSync(ctx context.Context, args []string, logger *pullpreview.Logge label := fs.String("label", "pullpreview", "Label to use for triggering preview deployments") deploymentVariant := fs.String("deployment-variant", "", "Deployment variant (4 chars max)") ttl := fs.String("ttl", "infinite", "Maximum time to live for deployments (e.g. 10h, 5d, infinite)") + templatedURL := fs.String("templated-url", "", "Template for the preview URL (use {{ pullpreview_url }} as placeholder)") commonFlags := registerCommonFlags(fs) leadingPath, parseArgs := splitLeadingPositional(args) fs.Parse(parseArgs) @@ -137,6 +138,7 @@ func runGithubSync(ctx context.Context, args []string, logger *pullpreview.Logge Label: *label, DeploymentVariant: *deploymentVariant, TTL: *ttl, + TemplatedURL: *templatedURL, Context: ctx, Common: commonOptions, } diff --git a/internal/pullpreview/github_sync.go b/internal/pullpreview/github_sync.go index fe468e0..24f90ce 100644 --- a/internal/pullpreview/github_sync.go +++ b/internal/pullpreview/github_sync.go @@ -486,8 +486,10 @@ func (g *GithubSync) Sync() error { return err } if upInstance != nil { - _ = g.updateGitHubStatus(statusDeployed, upInstance.URL()) - g.writeStepSummary(statusDeployed, action, upInstance.URL(), upInstance) + displayURL := g.applyTemplatedURL(upInstance.URL()) + _ = g.updateGitHubStatus(statusDeployed, displayURL) + g.writeStepSummary(statusDeployed, action, displayURL, upInstance) + g.writeTemplatedURLOutput(displayURL) } } return nil @@ -722,6 +724,30 @@ func (g *GithubSync) writeStepSummary(status deploymentStatus, action actionType } } +func (g *GithubSync) applyTemplatedURL(rawURL string) string { + tpl := strings.TrimSpace(g.opts.TemplatedURL) + if tpl == "" { + return rawURL + } + return strings.ReplaceAll(tpl, "{{ pullpreview_url }}", rawURL) +} + +func (g *GithubSync) writeTemplatedURLOutput(displayURL string) { + if strings.TrimSpace(g.opts.TemplatedURL) == "" { + return + } + path := os.Getenv("GITHUB_OUTPUT") + if path == "" { + return + } + f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return + } + defer f.Close() + fmt.Fprintf(f, "url=%s\n", displayURL) +} + func (g *GithubSync) workflowRunURL() string { server := strings.TrimSuffix(os.Getenv("GITHUB_SERVER_URL"), "/") runID := strings.TrimSpace(os.Getenv("GITHUB_RUN_ID")) diff --git a/internal/pullpreview/github_sync_test.go b/internal/pullpreview/github_sync_test.go index e355029..54fc254 100644 --- a/internal/pullpreview/github_sync_test.go +++ b/internal/pullpreview/github_sync_test.go @@ -503,6 +503,69 @@ func TestRenderStepSummaryForDeployedState(t *testing.T) { } } +func TestApplyTemplatedURL(t *testing.T) { + event := loadFixtureEvent(t, "github_event_labeled.json") + tests := []struct { + name string + templatedURL string + rawURL string + want string + }{ + { + name: "empty template returns raw URL", + templatedURL: "", + rawURL: "http://pr-1-ip-1-2-3-4.my.preview.run:80", + want: "http://pr-1-ip-1-2-3-4.my.preview.run:80", + }, + { + name: "template with custom path", + templatedURL: "{{ pullpreview_url }}/custom/path?key=value", + rawURL: "http://pr-1-ip-1-2-3-4.my.preview.run:80", + want: "http://pr-1-ip-1-2-3-4.my.preview.run:80/custom/path?key=value", + }, + { + name: "template with encoded params", + templatedURL: "{{ pullpreview_url }}/abc?redirectTo=def%2Fghi", + rawURL: "https://pr-1-ip-1-2-3-4.my.preview.run:443", + want: "https://pr-1-ip-1-2-3-4.my.preview.run:443/abc?redirectTo=def%2Fghi", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sync := newSync(event, GithubSyncOptions{Label: "pullpreview", TemplatedURL: tt.templatedURL, Common: CommonOptions{}}, &fakeGitHub{}, fakeProvider{running: true}) + got := sync.applyTemplatedURL(tt.rawURL) + if got != tt.want { + t.Fatalf("applyTemplatedURL()=%q, want %q", got, tt.want) + } + }) + } +} + +func TestSyncLabeledWithTemplatedURLUsesTemplateInComment(t *testing.T) { + t.Setenv("PULLPREVIEW_TEST", "1") + t.Setenv("GITHUB_SERVER_URL", "https://github.com") + t.Setenv("GITHUB_RUN_ID", "12345") + t.Setenv("GITHUB_STEP_SUMMARY", filepath.Join(t.TempDir(), "summary.md")) + event := loadFixtureEvent(t, "github_event_labeled.json") + client := &fakeGitHub{latestSHA: event.PullRequest.Head.SHA} + sync := newSync(event, GithubSyncOptions{ + Label: "pullpreview", + TemplatedURL: "{{ pullpreview_url }}/app?lang=en", + Common: CommonOptions{}, + }, client, fakeProvider{running: true}) + + if err := sync.Sync(); err != nil { + t.Fatalf("Sync() returned error: %v", err) + } + if len(client.updatedComments) == 0 { + t.Fatalf("expected PR comment update on deployed state") + } + lastComment := client.updatedComments[len(client.updatedComments)-1] + if !strings.Contains(lastComment, "/app?lang=en") { + t.Fatalf("expected templated URL with custom path in PR comment, got %q", lastComment) + } +} + func TestRunGithubSyncFromEnvironmentRunsDownForBranchPush(t *testing.T) { t.Setenv("PULLPREVIEW_TEST", "1") event := loadFixtureEvent(t, "github_event_push_solo_organization.json") diff --git a/internal/pullpreview/types.go b/internal/pullpreview/types.go index 7c79f45..9ba81b4 100644 --- a/internal/pullpreview/types.go +++ b/internal/pullpreview/types.go @@ -114,6 +114,7 @@ type GithubSyncOptions struct { Label string DeploymentVariant string TTL string + TemplatedURL string Context context.Context Common CommonOptions }