From f6489d2af195ec8694b39304ad0429257a32d9fc Mon Sep 17 00:00:00 2001 From: AdaAibaby <8872412+adababys@users.noreply.github.com> Date: Tue, 20 Oct 2026 14:13:55 +0800 Subject: [PATCH] feat(api): add TEMPLATES_DEFAULT_PUBLIC to force new templates public Self-hosted deployments sometimes want every template created by any team to be publicly visible platform-wide, rather than private-by-default. Today the public flag is hardcoded false for newly created templates (only inherited on same-alias rebuilds), with no config/env/request path to change it. Add an opt-in env var TEMPLATES_DEFAULT_PUBLIC, read in RegisterBuild (the single funnel for the V1/V2/V3 create paths) and applied after the rebuild-inheritance so it overrides the stored value for both the DB row and the API response. Unset / false / unparseable keeps the original private-by-default behavior, so this is fully backward compatible. --- .../api/internal/template/register_build.go | 22 +++++++++++++ .../template/register_build_public_test.go | 31 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 packages/api/internal/template/register_build_public_test.go diff --git a/packages/api/internal/template/register_build.go b/packages/api/internal/template/register_build.go index 0a0d528b5a..eb100dc37d 100644 --- a/packages/api/internal/template/register_build.go +++ b/packages/api/internal/template/register_build.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" "net/http" + "os" + "strconv" "github.com/google/uuid" "go.opentelemetry.io/otel" @@ -227,6 +229,14 @@ func registerBuild( data.Public = previous.Public } + // When TEMPLATES_DEFAULT_PUBLIC=true, force every newly created or rebuilt + // template to be public, so templates created by any team are visible + // platform-wide. Applied after the rebuild-inheritance above so it overrides + // any inherited value; a value of false (or unset) keeps the prior behavior. + if templatesDefaultPublic() { + data.Public = true + } + var clusterID *uuid.UUID if data.ClusterID != consts.LocalClusterID { clusterID = &data.ClusterID @@ -487,3 +497,15 @@ func registerBuild( Tags: tags, }, nil } + +// templatesDefaultPublic reports whether newly created templates should be +// forced public, controlled by the TEMPLATES_DEFAULT_PUBLIC environment +// variable. Unset, empty, or unparseable values default to false, preserving +// the original per-team-private behavior. +func templatesDefaultPublic() bool { + v, err := strconv.ParseBool(os.Getenv("TEMPLATES_DEFAULT_PUBLIC")) + if err != nil { + return false + } + return v +} diff --git a/packages/api/internal/template/register_build_public_test.go b/packages/api/internal/template/register_build_public_test.go new file mode 100644 index 0000000000..72b02e2026 --- /dev/null +++ b/packages/api/internal/template/register_build_public_test.go @@ -0,0 +1,31 @@ +package template + +import ( + "os" + "testing" +) + +func TestTemplatesDefaultPublic(t *testing.T) { + cases := []struct { + val string + set bool + want bool + }{ + {set: false, want: false}, + {val: "true", set: true, want: true}, + {val: "1", set: true, want: true}, + {val: "false", set: true, want: false}, + {val: "", set: true, want: false}, + {val: "garbage", set: true, want: false}, + } + for _, c := range cases { + if c.set { + t.Setenv("TEMPLATES_DEFAULT_PUBLIC", c.val) + } else { + os.Unsetenv("TEMPLATES_DEFAULT_PUBLIC") + } + if got := templatesDefaultPublic(); got != c.want { + t.Errorf("val=%q set=%v: got %v want %v", c.val, c.set, got, c.want) + } + } +}