Repository: pulumi/pulumi
Pull request #18627: Fetch Org-wide Project Templates from `app.pulumi.com`
Author: login: iwahbe
Created: 2025-02-18T12:22:36Z | Merged: 2025-03-25T18:52:38Z

This PR allows `pulumi new` to fetch org-wide templates from `app.pulumi.com` when they
are enabled. Part of https://github.com/pulumi/pulumi-service/issues/25732.

Conceptually, there are 2 parts to this PR:

- Wraps `workspace.RetrieveTemplates` in a new API specific for `cmd`:

    ```go
    // Create a new [Template] [Source] associated with a given [Scope].
    func New(
    	ctx context.Context, templateNamePathOrURL string, scope Scope,
    	templateKind workspace.TemplateKind, interactive bool,
    ) *Source { ... }
    ```

    This is necessary because `workspace.RetrieveTemplates` is in `sdk`: `"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"`. The new template download functionality needs to depend on various backend features such as `cmdBackend "github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/backend"`, which are obviously in `pkg`.

    The new `cmdTemplate.Template` abstraction allows for templates that are not present in the file system, such as those sourced by `(*Client) ListOrgTemplates`.

- Adjusts the template chooser and the rest of `cmd` to use the new `cmdTemplates.{Source,Template}` wrapper instead of `workspace.Template`. This is the mechanism that realizes the next two changes.


Diff:
diff --git a/changelog/pending/20250224--cli-new--allow-pulumi-new-to-use-templates-defined-in-the-pulumi-cloud.yaml b/changelog/pending/20250224--cli-new--allow-pulumi-new-to-use-templates-defined-in-the-pulumi-cloud.yaml
new file mode 100644
index 000000000000..9cb8d332fd44
--- /dev/null
+++ b/changelog/pending/20250224--cli-new--allow-pulumi-new-to-use-templates-defined-in-the-pulumi-cloud.yaml
@@ -0,0 +1,4 @@
+changes:
+- type: feat
+  scope: cli/new
+  description: Allow `pulumi new` to use templates defined in the Pulumi Cloud
diff --git a/pkg/cmd/pulumi/newcmd/new.go b/pkg/cmd/pulumi/newcmd/new.go
index 83c46f84a9ca..1df9227c5c32 100644
--- a/pkg/cmd/pulumi/newcmd/new.go
+++ b/pkg/cmd/pulumi/newcmd/new.go
@@ -18,9 +18,8 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"io"
 	"io/fs"
-	"net/http"
-	"net/url"
 	"os"
 	"path/filepath"
 	"strconv"
@@ -38,6 +37,7 @@ import (
 	"github.com/pulumi/pulumi/pkg/v3/backend/state"
 	cmdBackend "github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/backend"
 	cmdStack "github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/stack"
+	cmdTemplates "github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/templates"
 	"github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/ui"
 	"github.com/pulumi/pulumi/pkg/v3/engine"
 	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
@@ -56,7 +56,7 @@ import (
 type promptForValueFunc func(yes bool, valueType string, defaultValue string, secret bool,
 	isValidFn func(value string) error, opts display.Options) (string, error)
 
-type chooseTemplateFunc func(templates []workspace.Template, opts display.Options) (workspace.Template, error)
+type chooseTemplateFunc func(templates []cmdTemplates.Template, opts display.Options) (cmdTemplates.Template, error)
 
 type runtimeOptionsFunc func(ctx *plugin.Context, info *workspace.ProjectRuntimeInfo, main string,
 	opts display.Options, yes, interactive bool, prompt promptForValueFunc) (map[string]interface{}, error)
@@ -157,6 +157,15 @@ func runNew(ctx context.Context, args newArgs) error {
 		}
 	}
 
+	// Retrieve the template repo.
+	scope := cmdTemplates.ScopeAll
+	if args.offline {
+		scope = cmdTemplates.ScopeLocal
+	}
+	templateSource := cmdTemplates.New(ctx,
+		args.templateNameOrURL, scope, workspace.TemplateKindPulumiProject, args.interactive)
+	defer func() { contract.IgnoreError(templateSource.Close()) }()
+
 	if args.templateNameOrURL == "" {
 		// Try to read the current project
 		project, _, err := ws.ReadProject()
@@ -197,44 +206,29 @@ func runNew(ctx context.Context, args newArgs) error {
 			args.templateNameOrURL = conversationURL
 		}
 	}
-	// Retrieve the template repo.
-	var repo workspace.TemplateRepository
-	repo, err = workspace.RetrieveTemplates(
-		ctx, args.templateNameOrURL, args.offline, workspace.TemplateKindPulumiProject)
-	if err != nil {
-		// Bail on all errors unless its a 401 from a Pulumi Cloud backend...
-		if !errors.Is(err, workspace.ErrPulumiCloudUnauthorized) {
-			return err
-		}
-
-		// ...If the request has 401'd AND we've identified the backend as being a Pulumi Cloud instance, we can
-		// attempt to retrieve the template using the user's Pulumi Cloud credentials.
-		repo, err = retrievePrivatePulumiCloudTemplate(args.templateNameOrURL)
-		if err != nil {
-			return fmt.Errorf("retrieving private pulumi cloud template: %w", err)
-		}
-	}
-
-	defer func() {
-		contract.IgnoreError(repo.Delete())
-	}()
 
 	// List the templates from the repo.
-	templates, err := repo.Templates()
+	templates, err := templateSource.Templates()
 	if err != nil {
 		return err
 	}
 
-	var template workspace.Template
+	var cmdTemplate cmdTemplates.Template
 	if len(templates) == 0 {
 		return errors.New("no templates")
 	} else if len(templates) == 1 {
-		template = templates[0]
+		cmdTemplate = templates[0]
 	} else {
-		if template, err = args.chooseTemplate(templates, opts); err != nil {
+		if cmdTemplate, err = args.chooseTemplate(templates, opts); err != nil {
 			return err
 		}
 	}
+
+	template, err := cmdTemplate.Download(ctx)
+	if err != nil {
+		return err
+	}
+
 	if template.Errored() {
 		return fmt.Errorf("template '%s' is currently broken: %w", template.Name, template.Error)
 	}
@@ -491,49 +485,6 @@ func runNew(ctx context.Context, args newArgs) error {
 	return nil
 }
 
-// Retrieve a Private template from the given Pulumi Cloud URL **including an auth token for Pulumi Cloud**.
-//
-// workspace.GetAccount ensures the user has a valid session with the Pulumi Cloud backend.
-//   - If the user is not logged in, the login flow will be initiated.
-//   - If the user is not logged in and pulumi does not recognize the backend as a known workspace then
-//     the user will see an authentication error.
-func retrievePrivatePulumiCloudTemplate(templateURL string) (workspace.TemplateRepository, error) {
-	u, err := url.Parse(templateURL)
-	if err != nil {
-		return workspace.TemplateRepository{}, fmt.Errorf("parsing template URL: %w", err)
-	}
-	// Docs convention is to store the cloud URL with the protocol.
-	// e.g. `pulumi login https://api.pulumi.com` or `pulumi login https://api.acme.org`
-	templatePulumiCloudHost := "https://" + u.Host
-
-	account, err := workspace.GetAccount(templatePulumiCloudHost)
-	if err != nil {
-		return workspace.TemplateRepository{}, fmt.Errorf(
-			"looking up pulumi cloud backend %s: %w",
-			templatePulumiCloudHost,
-			err,
-		)
-	}
-
-	if account.AccessToken == "" {
-		return workspace.TemplateRepository{}, fmt.Errorf("no access token found for %s", templatePulumiCloudHost)
-	}
-
-	templateRepository, err := workspace.RetrieveZIPTemplates(templateURL, func(req *http.Request) {
-		req.Header.Set("Authorization", "token "+account.AccessToken)
-	})
-
-	if errors.Is(err, workspace.ErrPulumiCloudUnauthorized) {
-		return workspace.TemplateRepository{}, fmt.Errorf(
-			"unauthorized to access template at %s. You may not have access to this template or token may have expired",
-			templatePulumiCloudHost,
-		)
-	}
-
-	// Caller can handle other errors
-	return templateRepository, err
-}
-
 // isInteractive lets us force interactive mode for testing by setting PULUMI_TEST_INTERACTIVE.
 func isInteractive() bool {
 	test, ok := os.LookupEnv("PULUMI_TEST_INTERACTIVE")
@@ -551,16 +502,15 @@ func NewNewCmd() *cobra.Command {
 		promptRuntimeOptions: promptRuntimeOptions,
 	}
 
-	getTemplates := func(ctx context.Context) ([]workspace.Template, error) {
-		// Attempt to retrieve available templates.
-		repo, err := workspace.RetrieveTemplates(ctx, "", false /*offline*/, workspace.TemplateKindPulumiProject)
-		if err != nil {
-			logging.Warningf("could not retrieve templates: %v", err)
-			return []workspace.Template{}, err
+	getTemplates := func(ctx context.Context) ([]cmdTemplates.Template, io.Closer, error) {
+		scope := cmdTemplates.ScopeAll
+		if args.offline {
+			scope = cmdTemplates.ScopeLocal
 		}
-
-		// Get the list of templates.
-		return repo.Templates()
+		// Attempt to retrieve available templates.
+		s := cmdTemplates.New(ctx, "", scope, workspace.TemplateKindPulumiProject, args.interactive)
+		t, err := s.Templates()
+		return t, s, err
 	}
 
 	cmd := &cobra.Command{
@@ -622,16 +572,17 @@ func NewNewCmd() *cobra.Command {
 				args.templateNameOrURL = cliArgs[0]
 			}
 			if args.listTemplates {
-				templates, err := getTemplates(ctx)
+				templates, closer, err := getTemplates(ctx)
+				defer contract.IgnoreClose(closer)
 				if err != nil {
 					logging.Warningf("could not list templates: %v", err)
 					return err
 				}
 				available, _ := templatesToOptionArrayAndMap(templates)
-				fmt.Println("")
-				fmt.Println("Available Templates:")
+				fmt.Fprintln(cmd.OutOrStdout())
+				fmt.Fprintln(cmd.OutOrStdout(), "Available Templates:")
 				for _, t := range available {
-					fmt.Printf("  %s\n", t)
+					fmt.Fprintf(cmd.OutOrStdout(), "  %s\n", t)
 				}
 				return nil
 			}
@@ -651,7 +602,8 @@ func NewNewCmd() *cobra.Command {
 		// You'd think you could use cmd.Context() here but cobra doesn't set context on the cmd even though
 		// the parent help command has it. If https://github.com/spf13/cobra/issues/2240 gets fixed we can
 		// change back to cmd.Context() here.
-		templates, err := getTemplates(context.Background())
+		templates, closer, err := getTemplates(context.Background())
+		contract.IgnoreClose(closer)
 		if err != nil {
 			logging.Warningf("could not list templates: %v", err)
 			return
@@ -659,8 +611,8 @@ func NewNewCmd() *cobra.Command {
 
 		// If we have any templates, show them.
 		if len(templates) > 0 {
-			fmt.Println()
-			fmt.Printf("There are %d locally installed templates.\n", len(templates))
+			fmt.Fprintln(cmd.OutOrStdout())
+			fmt.Fprintf(cmd.OutOrStdout(), "There are %d available templates.\n", len(templates))
 		}
 	})
 
diff --git a/pkg/cmd/pulumi/newcmd/new_ai_test.go b/pkg/cmd/pulumi/newcmd/new_ai_test.go
index 06438d5ec145..e1f74821b1be 100644
--- a/pkg/cmd/pulumi/newcmd/new_ai_test.go
+++ b/pkg/cmd/pulumi/newcmd/new_ai_test.go
@@ -30,6 +30,8 @@ func TestErrorsOnNonHTTPBackend(t *testing.T) {
 		DoesProjectExistF: func(ctx context.Context, org string, name string) (bool, error) {
 			return name == projectName, nil
 		},
+		SupportsTemplatesF: func() bool { return false },
+		NameF:              func() string { return "mock" },
 	})
 
 	testNewArgs := newArgs{
diff --git a/pkg/cmd/pulumi/newcmd/new_test.go b/pkg/cmd/pulumi/newcmd/new_test.go
index f90e7ebf64b1..bf9f40687dd6 100644
--- a/pkg/cmd/pulumi/newcmd/new_test.go
+++ b/pkg/cmd/pulumi/newcmd/new_test.go
@@ -15,6 +15,7 @@
 package newcmd
 
 import (
+	"bytes"
 	"context"
 	"crypto/rand"
 	"encoding/hex"
@@ -28,6 +29,7 @@ import (
 	"github.com/pulumi/pulumi/pkg/v3/backend"
 	"github.com/pulumi/pulumi/pkg/v3/backend/display"
 	cmdBackend "github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/backend"
+	cmdTemplates "github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/templates"
 	"github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/ui"
 	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
 	"github.com/pulumi/pulumi/sdk/v3/go/common/resource/config"
@@ -252,6 +254,8 @@ func TestCreatingProjectWithExistingPromptedNameFails(t *testing.T) {
 		DoesProjectExistF: func(ctx context.Context, org string, name string) (bool, error) {
 			return name == projectName, nil
 		},
+		SupportsTemplatesF: func() bool { return false },
+		NameF:              func() string { return "mock" },
 	})
 
 	args := newArgs{
@@ -276,6 +280,8 @@ func TestGeneratingProjectWithExistingArgsSpecifiedNameSucceeds(t *testing.T) {
 		DoesProjectExistF: func(ctx context.Context, org string, name string) (bool, error) {
 			return true, nil
 		},
+		SupportsTemplatesF: func() bool { return false },
+		NameF:              func() string { return "mock" },
 	})
 
 	// Generate-only command is not creating any stacks, so don't bother with with the name uniqueness check.
@@ -307,6 +313,8 @@ func TestGeneratingProjectWithExistingPromptedNameSucceeds(t *testing.T) {
 		DoesProjectExistF: func(ctx context.Context, org string, name string) (bool, error) {
 			return true, nil
 		},
+		SupportsTemplatesF: func() bool { return false },
+		NameF:              func() string { return "mock" },
 	})
 
 	// Generate-only command is not creating any stacks, so don't bother with with the name uniqueness check.
@@ -403,6 +411,8 @@ func TestGeneratingProjectWithInvalidPromptedNameFails(t *testing.T) {
 		DoesProjectExistF: func(ctx context.Context, org string, name string) (bool, error) {
 			return true, nil
 		},
+		SupportsTemplatesF: func() bool { return false },
+		NameF:              func() string { return "mock" },
 	})
 
 	// Generate-only command is not creating any stacks, so don't bother with with the name uniqueness check.
@@ -744,14 +754,14 @@ func TestPulumiNewSetsTemplateTag(t *testing.T) {
 			chdir(t, tempdir)
 			uniqueProjectName := filepath.Base(tempdir) + "test"
 
-			chooseTemplateMock := func(templates []workspace.Template, opts display.Options,
-			) (workspace.Template, error) {
+			chooseTemplateMock := func(templates []cmdTemplates.Template, opts display.Options,
+			) (cmdTemplates.Template, error) {
 				for _, template := range templates {
-					if template.Name == tt.prompted {
+					if template.Name() == tt.prompted {
 						return template, nil
 					}
 				}
-				return workspace.Template{}, errors.New("template not found")
+				return nil, errors.New("template not found")
 			}
 
 			runtimeOptionsMock := func(ctx *plugin.Context, info *workspace.ProjectRuntimeInfo,
@@ -818,3 +828,229 @@ func TestPulumiPromptRuntimeOptions(t *testing.T) {
 	require.Equal(t, 1, len(proj.Runtime.Options()))
 	require.Equal(t, "someValue", proj.Runtime.Options()["someOption"])
 }
+
+//nolint:paralleltest // Sets a global mock backend
+func TestPulumiNewWithOrgTemplates(t *testing.T) {
+	mockBackendInstance(t, &backend.MockBackend{
+		SupportsTemplatesF: func() bool { return true },
+		CurrentUserF: func() (string, []string, *workspace.TokenInformation, error) {
+			return "fred", []string{"org1", "personal"}, nil, nil
+		},
+		ListTemplatesF: func(_ context.Context, orgName string) (apitype.ListOrgTemplatesResponse, error) {
+			switch orgName {
+			case "org1":
+				return apitype.ListOrgTemplatesResponse{
+					OrgHasTemplates: true,
+					Templates: map[string][]*apitype.PulumiTemplateRemote{
+						"github.com/example/foo": {
+							{
+								SourceName:  "Foo",
+								Name:        "template-1",
+								TemplateURL: "github.com/example/foo/template-1",
+								ProjectTemplate: apitype.ProjectTemplate{
+									DisplayName: "Display 1",
+									Description: "Describe 1",
+								},
+							},
+							{
+								SourceName:  "Foo",
+								Name:        "template-2",
+								TemplateURL: "github.com/example/foo/template-2",
+								ProjectTemplate: apitype.ProjectTemplate{
+									DisplayName: "Display 2",
+									Description: "Describe 2",
+								},
+							},
+						},
+					},
+				}, nil
+			case "personal":
+				return apitype.ListOrgTemplatesResponse{OrgHasTemplates: false}, nil
+			default:
+				return apitype.ListOrgTemplatesResponse{}, fmt.Errorf("unknown org %q", orgName)
+			}
+		},
+	})
+
+	newCmd := NewNewCmd()
+	var stdout, stderr bytes.Buffer
+	newCmd.SetOut(&stdout)
+	newCmd.SetErr(&stderr)
+	newCmd.SetArgs([]string{"--list-templates"})
+	err := newCmd.Execute()
+	require.NoError(t, err)
+
+	// Check that the normal prefix is still there
+	assert.Contains(t, stdout.String(), `
+Available Templates:
+`)
+	// Check that our org based templates are there
+	assert.Contains(t, stdout.String(), `
+  template-1                         Describe 1
+  template-2                         Describe 2
+`)
+
+	// Check that normal templates are there
+	assertTemplateContains(t, stdout.String(), `
+  aws-csharp                         A minimal AWS C# Pulumi program
+  aws-fsharp                         A minimal AWS F# Pulumi program
+  aws-go                             A minimal AWS Go Pulumi program
+  aws-java                           A minimal AWS Java Pulumi program
+  aws-javascript                     A minimal AWS JavaScript Pulumi program
+  aws-python                         A minimal AWS Python Pulumi program
+  aws-scala                          A minimal AWS Scala Pulumi program
+  aws-typescript                     A minimal AWS TypeScript Pulumi program
+  aws-visualbasic                    A minimal AWS VB.NET Pulumi program
+  aws-yaml                           A minimal AWS Pulumi YAML program
+`)
+	assert.Equal(t, "", stderr.String())
+}
+
+// TestPulumiNewWithoutPulumiAccessToken checks that we won't error if we run `pulumi new
+// --list-templates` without PULUMI_ACCESS_TOKEN set.
+//
+//nolint:paralleltest // Changes environmental variables
+func TestPulumiNewWithoutPulumiAccessToken(t *testing.T) {
+	t.Setenv("PULUMI_ACCESS_TOKEN", "")
+
+	newCmd := NewNewCmd()
+	var stdout, stderr bytes.Buffer
+	newCmd.SetOut(&stdout)
+	newCmd.SetErr(&stderr)
+	newCmd.SetArgs([]string{"--list-templates"})
+	err := newCmd.Execute()
+	require.NoError(t, err)
+
+	assert.Contains(t, stdout.String(), `
+Available Templates:
+`)
+	assertTemplateContains(t, stdout.String(), `
+  aws-csharp                              A minimal AWS C# Pulumi program
+  aws-fsharp                              A minimal AWS F# Pulumi program
+  aws-go                                  A minimal AWS Go Pulumi program
+  aws-java                                A minimal AWS Java Pulumi program
+  aws-javascript                          A minimal AWS JavaScript Pulumi program
+  aws-python                              A minimal AWS Python Pulumi program
+  aws-scala                               A minimal AWS Scala Pulumi program
+  aws-typescript                          A minimal AWS TypeScript Pulumi program
+  aws-visualbasic                         A minimal AWS VB.NET Pulumi program
+  aws-yaml                                A minimal AWS Pulumi YAML program
+`)
+	assert.Equal(t, "", stderr.String())
+}
+
+//nolint:paralleltest // Sets a global mock backend
+func TestPulumiNewWithoutTemplateSupport(t *testing.T) {
+	mockBackendInstance(t, &backend.MockBackend{
+		SupportsTemplatesF: func() bool { return false },
+		NameF:              func() string { return "mock" },
+	})
+
+	newCmd := NewNewCmd()
+	var stdout, stderr bytes.Buffer
+	newCmd.SetOut(&stdout)
+	newCmd.SetErr(&stderr)
+	newCmd.SetArgs([]string{"--list-templates"})
+	err := newCmd.Execute()
+	require.NoError(t, err)
+
+	// Check that normal templates are there
+	assert.Contains(t, stdout.String(), `
+Available Templates:
+  aiven-go                           A minimal Aiven Go Pulumi program
+`)
+	assert.Equal(t, "", stderr.String())
+}
+
+//nolint:paralleltest // Sets a global mock backend, changes the directory
+func TestPulumiNewOrgTemplate(t *testing.T) {
+	tempdir := tempProjectDir(t)
+	chdir(t, tempdir)
+	mockBackendInstance(t, &backend.MockBackend{
+		SupportsTemplatesF: func() bool { return true },
+		CurrentUserF: func() (string, []string, *workspace.TokenInformation, error) {
+			return "fred", []string{"org1"}, nil, nil
+		},
+		ListTemplatesF: func(_ context.Context, orgName string) (apitype.ListOrgTemplatesResponse, error) {
+			switch orgName {
+			case "org1":
+				return apitype.ListOrgTemplatesResponse{
+					OrgHasTemplates: true,
+					Templates: map[string][]*apitype.PulumiTemplateRemote{
+						"github.com/example/foo": {
+							{
+								SourceName:  "Foo",
+								Name:        "template-1",
+								TemplateURL: "https://github.com/example/foo/template-1",
+								ProjectTemplate: apitype.ProjectTemplate{
+									DisplayName: "Display 1",
+									Description: "Describe 1",
+								},
+							},
+						},
+					},
+				}, nil
+			default:
+				return apitype.ListOrgTemplatesResponse{}, fmt.Errorf("unknown org %q", orgName)
+			}
+		},
+		DownloadTemplateF: func(_ context.Context, orgName, templateSource string) (backend.TarReaderCloser, error) {
+			if orgName != "org1" {
+				return nil, fmt.Errorf("unknown org %q", orgName)
+			}
+			if templateSource != "https://github.com/example/foo/template-1" {
+				return nil, fmt.Errorf("unknown template source %q", templateSource)
+			}
+
+			return backend.MockTarReader{
+				"Pulumi.yaml": {Content: `name: ${PROJECT}
+description: ${DESCRIPTION}
+runtime: yaml
+template:
+  description: Describe 1
+
+resources:
+  # Create an AWS resource (S3 Bucket)
+  my-bucket:
+    type: aws:s3:BucketV2
+`},
+			}, nil
+		},
+	})
+
+	newCmd := NewNewCmd()
+	var stdout, stderr bytes.Buffer
+	newCmd.SetOut(&stdout)
+	newCmd.SetErr(&stderr)
+	newCmd.SetArgs([]string{"template-1", "--generate-only", "--yes"})
+	err := newCmd.Execute()
+	require.NoError(t, err)
+
+	proj := loadProject(t, tempdir)
+	require.Equal(t, "yaml", proj.Runtime.Name())
+}
+
+// Assert that actual contains the template rows show in expected.
+//
+// This parsing based comparison is necessary since raw string comparison is unstable
+// under insertion due to white-space changes.
+func assertTemplateContains(t *testing.T, actual, expected string) {
+	parse := func(stdout string) []struct{ name, description string } {
+		stdout = strings.TrimPrefix(stdout, `Available Templates:
+`)
+		lines := strings.Split(strings.TrimSpace(stdout), "\n")
+		out := make([]struct{ name, description string }, len(lines))
+		for i, l := range lines {
+			parts := strings.Fields(l)
+			out[i].name = parts[0]
+			out[i].description = strings.Join(parts[1:], " ")
+		}
+		return out
+	}
+
+	expectedP := parse(expected)
+	actualP := parse(actual)
+	for _, e := range expectedP {
+		assert.Contains(t, actualP, e)
+	}
+}
diff --git a/pkg/cmd/pulumi/newcmd/template.go b/pkg/cmd/pulumi/newcmd/template.go
index dd2820db0d10..fb43413554e3 100644
--- a/pkg/cmd/pulumi/newcmd/template.go
+++ b/pkg/cmd/pulumi/newcmd/template.go
@@ -25,11 +25,10 @@ import (
 
 	"github.com/pulumi/pulumi/pkg/v3/backend/display"
 	"github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/cmd"
+	cmdTemplates "github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/templates"
 	"github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/ui"
-	"github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors"
-	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
-
 	pkgWorkspace "github.com/pulumi/pulumi/pkg/v3/workspace"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors"
 )
 
 const (
@@ -37,10 +36,10 @@ const (
 )
 
 // ChooseTemplate will prompt the user to choose amongst the available templates.
-func ChooseTemplate(templates []workspace.Template, opts display.Options) (workspace.Template, error) {
+func ChooseTemplate(templates []cmdTemplates.Template, opts display.Options) (cmdTemplates.Template, error) {
 	const chooseTemplateErr = "no template selected; please use `pulumi new` to choose one"
 	if !opts.IsInteractive {
-		return workspace.Template{}, errors.New(chooseTemplateErr)
+		return nil, errors.New(chooseTemplateErr)
 	}
 
 	// Customize the prompt a little bit (and disable color since it doesn't match our scheme).
@@ -58,7 +57,7 @@ func ChooseTemplate(templates []workspace.Template, opts display.Options) (works
 		Options:  options,
 		PageSize: pageSize,
 	}, &option, ui.SurveyIcons(opts.Color)); err != nil {
-		return workspace.Template{}, errors.New(chooseTemplateErr)
+		return nil, errors.New(chooseTemplateErr)
 	}
 
 	return optionToTemplateMap[option], nil
@@ -66,31 +65,32 @@ func ChooseTemplate(templates []workspace.Template, opts display.Options) (works
 
 // templatesToOptionArrayAndMap returns an array of option strings and a map of option strings to templates.
 // Each option string is made up of the template name and description with some padding in between.
-func templatesToOptionArrayAndMap(templates []workspace.Template) ([]string, map[string]workspace.Template) {
+func templatesToOptionArrayAndMap(templates []cmdTemplates.Template) ([]string, map[string]cmdTemplates.Template) {
 	// Find the longest name length. Used to add padding between the name and description.
 	maxNameLength := 0
 	for _, template := range templates {
-		if len(template.Name) > maxNameLength {
-			maxNameLength = len(template.Name)
+		if len(template.Name()) > maxNameLength {
+			maxNameLength = len(template.Name())
 		}
 	}
 
 	// Build the array and map.
 	var options []string
 	var brokenOptions []string
-	nameToTemplateMap := make(map[string]workspace.Template)
+	nameToTemplateMap := make(map[string]cmdTemplates.Template)
 	for _, template := range templates {
+		projectDescription := template.ProjectDescription()
 		// If template is broken, indicate it in the project description.
-		if template.Errored() {
-			template.ProjectDescription = BrokenTemplateDescription
+		if template.Error() != nil {
+			projectDescription = BrokenTemplateDescription
 		}
 
 		// Create the option string that combines the name, padding, and description.
-		desc := pkgWorkspace.ValueOrDefaultProjectDescription("", template.ProjectDescription, template.Description)
-		option := fmt.Sprintf(fmt.Sprintf("%%%ds    %%s", -maxNameLength), template.Name, desc)
+		desc := pkgWorkspace.ValueOrDefaultProjectDescription("", projectDescription, template.Description())
+		option := fmt.Sprintf(fmt.Sprintf("%%%ds    %%s", -maxNameLength), template.Name(), desc)
 
 		nameToTemplateMap[option] = template
-		if template.Errored() {
+		if template.Error() != nil {
 			brokenOptions = append(brokenOptions, option)
 		} else {
 			options = append(options, option)
diff --git a/pkg/cmd/pulumi/operations/up.go b/pkg/cmd/pulumi/operations/up.go
index bbd64ba96317..32f82b913174 100644
--- a/pkg/cmd/pulumi/operations/up.go
+++ b/pkg/cmd/pulumi/operations/up.go
@@ -34,6 +34,7 @@ import (
 	newcmd "github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/newcmd"
 	"github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/plan"
 	cmdStack "github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/stack"
+	cmdTemplates "github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/templates"
 	"github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/ui"
 	"github.com/pulumi/pulumi/pkg/v3/engine"
 	"github.com/pulumi/pulumi/pkg/v3/resource/autonaming"
@@ -265,16 +266,15 @@ func NewUpCmd() *cobra.Command {
 		cmd *cobra.Command,
 	) error {
 		// Retrieve the template repo.
-		repo, err := workspace.RetrieveTemplates(ctx, templateNameOrURL, false, workspace.TemplateKindPulumiProject)
-		if err != nil {
-			return err
-		}
+		templateSource := cmdTemplates.New(ctx,
+			templateNameOrURL, cmdTemplates.ScopeAll,
+			workspace.TemplateKindPulumiProject, cmdutil.Interactive())
 		defer func() {
-			contract.IgnoreError(repo.Delete())
+			contract.IgnoreError(templateSource.Close())
 		}()
 
 		// List the templates from the repo.
-		templates, err := repo.Templates()
+		templates, err := templateSource.Templates()
 		if err != nil {
 			return err
 		}
@@ -283,9 +283,17 @@ func NewUpCmd() *cobra.Command {
 		if len(templates) == 0 {
 			return errors.New("no template found")
 		} else if len(templates) == 1 {
-			template = templates[0]
+			template, err = templates[0].Download(ctx)
+			if err != nil {
+				return err
+			}
 		} else {
-			if template, err = newcmd.ChooseTemplate(templates, opts.Display); err != nil {
+			t, err := newcmd.ChooseTemplate(templates, opts.Display)
+			if err != nil {
+				return err
+			}
+			template, err = t.Download(ctx)
+			if err != nil {
 				return err
 			}
 		}
diff --git a/pkg/cmd/pulumi/templates/org.go b/pkg/cmd/pulumi/templates/org.go
new file mode 100644
index 000000000000..ca9efff34693
--- /dev/null
+++ b/pkg/cmd/pulumi/templates/org.go
@@ -0,0 +1,243 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package templates
+
+import (
+	"archive/tar"
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"io/fs"
+	"math"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/pkg/v3/backend"
+	"github.com/pulumi/pulumi/pkg/v3/backend/display"
+	cmdBackend "github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/backend"
+	pkgWorkspace "github.com/pulumi/pulumi/pkg/v3/workspace"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+func (s *Source) getOrgTemplates(
+	ctx context.Context, templateName string,
+	interactive bool, wg *sync.WaitGroup,
+) {
+	ws := pkgWorkspace.Instance
+	project, _, err := ws.ReadProject()
+	if err != nil && !errors.Is(err, workspace.ErrProjectNotFound) {
+		s.addError(fmt.Errorf("could not read the current project: %w", err))
+		return
+	}
+
+	b, err := cmdBackend.CurrentBackend(ctx, ws, cmdBackend.DefaultLoginManager, project, display.Options{
+		Color:         cmdutil.GetGlobalColorization(),
+		IsInteractive: interactive,
+	})
+	if err != nil {
+		if !errors.Is(err, backend.MissingEnvVarForNonInteractiveError{}) {
+			s.addError(fmt.Errorf("could not get the current backend: %w", err))
+		}
+		logging.Infof("could not get a backend for org templates")
+		return
+	}
+	if !b.SupportsTemplates() {
+		logging.Infof("%s does not support Org Templates", b.Name())
+		return
+	}
+
+	logging.Infof("Listing Org Templates from the cloud")
+	user, orgs, _, err := b.CurrentUser()
+	if err != nil {
+		s.addError(fmt.Errorf("could not get the current user: %w", err))
+		return
+	} else if user == "" {
+		return // No current user - so don't proceed.
+	}
+
+	alreadySeenSourceURLs := map[string]struct{}{}
+
+	handleOrg := func(org string) {
+		defer wg.Done()
+		logging.Infof("Checking for templates from %q", org)
+		orgTemplates, err := b.ListTemplates(ctx, org)
+		if apiError := new(apitype.ErrorResponse); errors.As(err, &apiError) {
+			// This is what happens when we try to access org templates for an org that hasn't enabled org templates.
+			if apiError.Code == 402 {
+				logging.Infof("%q does not have access to org templates (code=%d)", org, apiError.Code)
+				return
+			}
+		} else if err != nil {
+			s.addError(fmt.Errorf("list templates: %w", err))
+			logging.Warningf("Failed to get templates from %q: %s", org, err.Error())
+			return
+		} else if orgTemplates.HasAccessError {
+			logging.Warningf("Failed to get templates from %q: Access Denied\n"+
+				"Please check that %s can access all template sources", org, b.Name())
+			return
+		} else if orgTemplates.HasUpstreamError {
+			// This is a catch-all error indicating only that *something* went
+			// wrong with fetching templates for an org.
+			logging.Warningf("Failed to get templates from %q: %s could not download the template", org, b.Name())
+			return
+		}
+
+		for source, sourceTemplates := range orgTemplates.Templates {
+			logging.Infof("sourcing templates from %q", source)
+			for _, template := range sourceTemplates {
+				// These template are maintained using https://github.com/pulumi/templates, and are
+				// ingested without going through the Pulumi Cloud.
+				//
+				//
+				if strings.HasPrefix(template.TemplateURL, "https://github.com/pulumi/templates") {
+					continue
+				}
+
+				// Check if we already have this template from another source.
+				if _, ok := alreadySeenSourceURLs[template.TemplateURL]; ok {
+					// Skip a template that we have already seen.
+					continue
+				}
+				alreadySeenSourceURLs[template.TemplateURL] = struct{}{}
+
+				// If we are searching for a template of a specific name,
+				// only match templates of that name.
+				if templateName != "" && templateName != template.Name {
+					logging.V(10).Infof("skipping template %q", template.Name)
+					continue
+				}
+
+				logging.V(10).Infof("adding template %q", template.Name)
+				s.addTemplate(orgTemplate{
+					t:       template,
+					org:     org,
+					source:  s,
+					backend: b,
+				})
+			}
+		}
+	}
+
+	for _, org := range orgs {
+		wg.Add(1)
+		go handleOrg(org)
+	}
+}
+
+type orgTemplate struct {
+	t       *apitype.PulumiTemplateRemote
+	org     string
+	source  *Source
+	backend backend.Backend
+}
+
+func (t orgTemplate) Name() string               { return t.t.Name }
+func (t orgTemplate) Description() string        { return "" }
+func (t orgTemplate) ProjectDescription() string { return t.t.ProjectTemplate.Description }
+func (t orgTemplate) Error() error               { return nil }
+func (t orgTemplate) Download(ctx context.Context) (workspace.Template, error) {
+	templateDir, err := os.MkdirTemp("", "pulumi-template-")
+	if err != nil {
+		return workspace.Template{}, err
+	}
+	// Having created a template directory, we now add it to the list of directories to close.
+	t.source.addCloser(func() error { return os.RemoveAll(templateDir) })
+
+	tarReader, err := t.backend.DownloadTemplate(ctx, t.org, t.t.TemplateURL)
+	if err != nil {
+		return workspace.Template{}, err
+	}
+	if err := errors.Join(
+		writeTar(ctx, tarReader.Tar(), templateDir),
+		tarReader.Close(),
+	); err != nil {
+		return workspace.Template{}, err
+	}
+	logging.Infof("downloaded %q into %q", t.t.Name, templateDir)
+
+	return workspace.LoadTemplate(templateDir)
+}
+
+func writeTar(ctx context.Context, reader *tar.Reader, dst string) error {
+	for {
+		// If the context has been canceled or has timed out, return.
+		if err := ctx.Err(); err != nil {
+			return err
+		}
+
+		header, err := reader.Next()
+		if errors.Is(err, io.EOF) {
+			return nil
+		} else if err != nil {
+			return err
+		}
+
+		logging.V(8).Infof("Decompressing %q", header.Name)
+
+		path := filepath.Clean(header.Name)
+		if !filepath.IsLocal(path) {
+			return fmt.Errorf("refusing to write non-local path %q", path)
+		}
+
+		target := filepath.Join(dst, path)
+
+		// Ensure that we can write the directory
+		if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
+			return err
+		}
+
+		switch header.Typeflag {
+		case tar.TypeDir:
+			if header.Mode > math.MaxUint32 {
+				return fmt.Errorf("invalid file mode for %q: %02x", header.Name, header.Mode)
+			}
+
+			fileMode := os.FileMode(header.Mode) //nolint:gosec // We checked the overflow
+			err := os.Mkdir(target, fileMode)
+			if err != nil && !errors.Is(err, fs.ErrExist) {
+				return err
+			}
+
+		case tar.TypeReg:
+			if header.Mode > math.MaxUint32 {
+				return fmt.Errorf("invalid file mode for %q: %02x", header.Name, header.Mode)
+			}
+
+			fileMode := os.FileMode(header.Mode) //nolint:gosec // We checked the overflow
+			f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, fileMode)
+			if err != nil {
+				return err
+			}
+
+			if err := func() (err error) {
+				// We wrap this defer in an immediately invoked function
+				// so that the file is closed within this loop iteration,
+				// not at the end of writeTar.
+				defer func() { err = errors.Join(err, f.Close()) }()
+				// Write the tar file into f
+				_, err = io.Copy(f, reader)
+				return err
+			}(); err != nil {
+				return err
+			}
+		}
+	}
+}
diff --git a/pkg/cmd/pulumi/templates/templates.go b/pkg/cmd/pulumi/templates/templates.go
new file mode 100644
index 000000000000..43d4440a92de
--- /dev/null
+++ b/pkg/cmd/pulumi/templates/templates.go
@@ -0,0 +1,211 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package templates adds an abstraction for project templates that may be local or
+// remote.
+//
+// All templates are convertible into [workspace.Template].
+package templates
+
+import (
+	"context"
+	"errors"
+	"io/fs"
+	"os"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+// Source provides access to a set of project templates, any set of which may be present on
+// disk.
+//
+// Source is responsible for cleaning up old templates, and should always be [Close]d when
+// created.
+type Source struct {
+	templates    []Template
+	errorOnEmpty []error
+	errors       []error
+
+	// cancel holds the function to cancel the context passed into the [New] that created the source.
+	cancel context.CancelFunc
+	// closers holds a list of functions to be invoked when the Source is closed.
+	closers []func() error
+	closed  bool
+
+	// m should be held whenever Source is mutated.
+	m  sync.Mutex
+	wg sync.WaitGroup
+}
+
+// Templates lists the templates available to the [Source].
+//
+// Templates *does not* produce a sorted list. If templates need to be sorted, then the
+// caller is responsible for sorting them.
+func (s *Source) Templates() ([]Template, error) {
+	s.wg.Wait() // Wait to ensure that all templates have been fetched before returning the template list.
+
+	s.lockOpen("read templates")
+	defer s.m.Unlock()
+	if err := errors.Join(s.errors...); err != nil {
+		return nil, err
+	}
+	if len(s.templates) == 0 {
+		return nil, errors.Join(s.errorOnEmpty...)
+	}
+	return s.templates, nil
+}
+
+func (s *Source) addTemplate(t Template) {
+	contract.Assertf(t != nil, "We should never return nil templates")
+	s.lockOpen("add template")
+	s.templates = append(s.templates, t)
+	s.m.Unlock()
+}
+
+func (s *Source) addCloser(f func() error) {
+	s.lockOpen("add closer")
+	s.closers = append(s.closers, f)
+	s.m.Unlock()
+}
+
+func (s *Source) addError(err error) {
+	s.lockOpen("add error")
+	s.errors = append(s.errors, err)
+	s.m.Unlock()
+}
+
+func (s *Source) addErrorOnEmpty(err error) {
+	s.lockOpen("add error on empty")
+	s.errorOnEmpty = append(s.errorOnEmpty, err)
+	s.m.Unlock()
+}
+
+func (s *Source) lockOpen(action string) {
+	s.m.Lock()
+	contract.Assertf(!s.closed, "Attempted to act on closed source: "+action)
+}
+
+// Close cleans up the [Source] and any associated templates.
+//
+// Close should always be called when [Source] is dropped.
+func (s *Source) Close() error {
+	s.cancel()
+
+	s.wg.Wait() // Wait to ensure that all templates have been fetched so all closers are visible.
+
+	s.lockOpen("close")
+	defer s.m.Unlock()
+	s.closed = true
+	errs := make([]error, len(s.closers))
+	for i, f := range s.closers {
+		errs[i] = f()
+	}
+	return errors.Join(errs...)
+}
+
+type Template interface {
+	Name() string
+	Description() string
+	ProjectDescription() string
+	Error() error
+	Download(ctx context.Context) (workspace.Template, error)
+}
+
+// SearchScope dictates where [New] will search for templates.
+type SearchScope struct{ kind string }
+
+var (
+	// ScopeAll searches for templates in all available locations.
+	ScopeAll = SearchScope{}
+	// ScopeLocal searches for templates only locally (on disk).
+	ScopeLocal = SearchScope{"local"}
+)
+
+// Create a new [Template] [Source] associated with a given [SearchScope].
+func New(
+	ctx context.Context, templateNamePathOrURL string, scope SearchScope,
+	templateKind workspace.TemplateKind, interactive bool,
+) *Source {
+	return newImpl(
+		ctx, templateNamePathOrURL, scope,
+		templateKind, interactive,
+		workspace.RetrieveTemplates,
+	)
+}
+
+// The impl for [New].
+//
+// having a separate impl function allows mocking out getWorkspaceTemplates.
+func newImpl(
+	ctx context.Context, templateNamePathOrURL string, scope SearchScope,
+	templateKind workspace.TemplateKind, interactive bool,
+	getWorkspaceTemplates getWorkspaceTemplateFunc,
+) *Source {
+	var source Source
+	ctx, cancel := context.WithCancel(ctx)
+	source.cancel = cancel
+
+	if scope == ScopeAll || scope == ScopeLocal {
+		source.wg.Add(1)
+		go func() {
+			source.getWorkspaceTemplates(ctx, templateNamePathOrURL, scope, templateKind, &source.wg, getWorkspaceTemplates)
+			source.wg.Done()
+		}()
+	}
+
+	if scope == ScopeAll && templateKind == workspace.TemplateKindPulumiProject && isTemplateName(templateNamePathOrURL) {
+		source.wg.Add(1)
+		go func() {
+			source.getOrgTemplates(ctx, templateNamePathOrURL, interactive, &source.wg)
+			source.wg.Done()
+		}()
+	}
+
+	return &source
+}
+
+func isTemplateName(templateNamePathOrURL string) bool {
+	return !workspace.IsTemplateURL(templateNamePathOrURL) &&
+		!isTemplatePath(templateNamePathOrURL)
+}
+
+func isTemplatePath(query string) bool {
+	_, err := os.Stat(query)
+	if errors.Is(err, fs.ErrNotExist) {
+		if looksLikePath(query) {
+			const msg = "%q looks like a file path, but no file exists. Assuming to be a template name"
+			logging.Warningf(msg, query)
+		}
+		return false
+	} else if err != nil {
+		logging.Warningf("unable to stat %q: %s", query, err.Error())
+		return false
+	}
+
+	// query does point to a local file.
+
+	if !looksLikePath(query) {
+		const msg = `Assuming %[1]q is a file path, use "./%[1]s" to be unambiguous`
+		logging.Warningf(msg, query)
+	}
+	return err == nil
+}
+
+func looksLikePath(query string) bool {
+	return strings.HasPrefix(query, "./") || strings.HasPrefix(query, "/")
+}
diff --git a/pkg/cmd/pulumi/templates/templates_test.go b/pkg/cmd/pulumi/templates/templates_test.go
new file mode 100644
index 000000000000..8ada843209bd
--- /dev/null
+++ b/pkg/cmd/pulumi/templates/templates_test.go
@@ -0,0 +1,291 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package templates
+
+import (
+	"context"
+	"errors"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/pulumi/pulumi/pkg/v3/backend"
+	cmdBackend "github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/backend"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+//nolint:paralleltest // replaces global backend instance
+func TestFilterOnName(t *testing.T) {
+	ctx := testContext(t)
+
+	template1 := &apitype.PulumiTemplateRemote{
+		ProjectTemplate: apitype.ProjectTemplate{},
+		Name:            "name1",
+		SourceName:      "source1",
+		TemplateURL:     "example.com/source1/name1",
+	}
+
+	setBackend(t, &backend.MockBackend{
+		SupportsTemplatesF: func() bool { return true },
+		CurrentUserF: func() (string, []string, *workspace.TokenInformation, error) {
+			return "doe", []string{"org1"}, &workspace.TokenInformation{}, nil
+		},
+		ListTemplatesF: func(_ context.Context, orgName string) (apitype.ListOrgTemplatesResponse, error) {
+			assert.Equal(t, "org1", orgName)
+
+			return apitype.ListOrgTemplatesResponse{
+				Templates: map[string][]*apitype.PulumiTemplateRemote{
+					"source1": {template1},
+					"source2": {
+						{
+							ProjectTemplate: apitype.ProjectTemplate{},
+							Name:            "name2",
+							SourceName:      "source2",
+							TemplateURL:     "example.com/source2/name1",
+						},
+					},
+				},
+				OrgHasTemplates: true,
+			}, nil
+		},
+	})
+
+	source := newImpl(ctx, "name1",
+		ScopeAll, workspace.TemplateKindPulumiProject,
+		false, templateRepository(workspace.TemplateRepository{}, workspace.TemplateNotFoundError{}),
+	)
+
+	template, err := source.Templates()
+	require.NoError(t, err)
+	assert.Equal(t,
+		[]Template{orgTemplate{t: template1, org: "org1", source: source, backend: cmdBackend.BackendInstance}},
+		template)
+}
+
+//nolint:paralleltest // replaces global backend instance
+func TestMultipleTemplateSources(t *testing.T) {
+	ctx := testContext(t)
+
+	template1 := &apitype.PulumiTemplateRemote{
+		ProjectTemplate: apitype.ProjectTemplate{},
+		Name:            "name1",
+		SourceName:      "source1",
+		TemplateURL:     "example.com/source1/name1",
+	}
+
+	template2 := &apitype.PulumiTemplateRemote{
+		ProjectTemplate: apitype.ProjectTemplate{},
+		Name:            "name2",
+		SourceName:      "source2",
+		TemplateURL:     "example.com/source2/name1",
+	}
+
+	setBackend(t, &backend.MockBackend{
+		SupportsTemplatesF: func() bool { return true },
+		CurrentUserF: func() (string, []string, *workspace.TokenInformation, error) {
+			return "doe", []string{"org1"}, &workspace.TokenInformation{}, nil
+		},
+		ListTemplatesF: func(_ context.Context, orgName string) (apitype.ListOrgTemplatesResponse, error) {
+			assert.Equal(t, "org1", orgName)
+
+			return apitype.ListOrgTemplatesResponse{
+				Templates: map[string][]*apitype.PulumiTemplateRemote{
+					"source1": {template1},
+					"source2": {template2},
+				},
+				OrgHasTemplates: true,
+			}, nil
+		},
+	})
+
+	repoTemplateDir := t.TempDir()
+	subdir := filepath.Join(repoTemplateDir, "sub")
+	require.NoError(t, os.Mkdir(subdir, 0o700))
+	require.NoError(t, os.WriteFile(filepath.Join(subdir, "Pulumi.yaml"), []byte(`name: template3
+runtime: dotnet
+description: An ASP.NET application running a simple container in a EKS Cluster
+`), 0o600))
+	repoTemplates := templateRepository(workspace.TemplateRepository{
+		Root:         repoTemplateDir,
+		SubDirectory: subdir,
+	}, nil)
+
+	source := newImpl(ctx, "",
+		ScopeAll, workspace.TemplateKindPulumiProject,
+		false, repoTemplates,
+	)
+
+	template, err := source.Templates()
+	require.NoError(t, err)
+	assert.ElementsMatch(t,
+		[]Template{
+			workspaceTemplate{t: workspace.Template{
+				Dir:                subdir,
+				Name:               "sub",
+				ProjectName:        "template3",
+				ProjectDescription: "An ASP.NET application running a simple container in a EKS Cluster",
+			}},
+			orgTemplate{t: template1, org: "org1", source: source, backend: cmdBackend.BackendInstance},
+			orgTemplate{t: template2, org: "org1", source: source, backend: cmdBackend.BackendInstance},
+		},
+		template)
+}
+
+//nolint:paralleltest // replaces global backend instance
+func TestSurfaceListTemplateErrors(t *testing.T) {
+	ctx := testContext(t)
+
+	somethingWentWrong := errors.New("something went wrong")
+
+	setBackend(t, &backend.MockBackend{
+		SupportsTemplatesF: func() bool { return true },
+		CurrentUserF: func() (string, []string, *workspace.TokenInformation, error) {
+			return "doe", []string{"org1"}, &workspace.TokenInformation{}, nil
+		},
+		ListTemplatesF: func(_ context.Context, orgName string) (apitype.ListOrgTemplatesResponse, error) {
+			assert.Equal(t, "org1", orgName)
+			return apitype.ListOrgTemplatesResponse{}, somethingWentWrong
+		},
+	})
+
+	source := newImpl(ctx, "name1",
+		ScopeAll, workspace.TemplateKindPulumiProject,
+		false, templateRepository(workspace.TemplateRepository{}, workspace.TemplateNotFoundError{}),
+	)
+
+	_, err := source.Templates()
+	assert.ErrorIs(t, err, somethingWentWrong)
+}
+
+//nolint:paralleltest // replaces global backend instance
+func TestSurfaceOnEmptyError(t *testing.T) {
+	ctx := testContext(t)
+
+	setBackend(t, &backend.MockBackend{
+		SupportsTemplatesF: func() bool { return true },
+		CurrentUserF: func() (string, []string, *workspace.TokenInformation, error) {
+			return "doe", []string{"org1"}, &workspace.TokenInformation{}, nil
+		},
+		ListTemplatesF: func(_ context.Context, orgName string) (apitype.ListOrgTemplatesResponse, error) {
+			assert.Equal(t, "org1", orgName)
+			return apitype.ListOrgTemplatesResponse{}, nil
+		},
+	})
+
+	source := newImpl(ctx, "name1",
+		ScopeAll, workspace.TemplateKindPulumiProject,
+		false, templateRepository(workspace.TemplateRepository{}, workspace.TemplateNotFoundError{}),
+	)
+
+	_, err := source.Templates()
+	var expected workspace.TemplateNotFoundError
+	assert.ErrorAsf(t, err, &expected, "what's in %#v", source.errorOnEmpty)
+}
+
+//nolint:paralleltest // replaces global backend instance
+func TestOrgTemplateDownload(t *testing.T) {
+	ctx := testContext(t)
+
+	template1 := &apitype.PulumiTemplateRemote{
+		ProjectTemplate: apitype.ProjectTemplate{},
+		Name:            "name1",
+		SourceName:      "source1",
+		TemplateURL:     "example.com/source1/name1",
+	}
+
+	pulumiYAML := `name: template1
+runtime: dotnet
+description: An ASP.NET application running a simple container in a EKS Cluster
+`
+	anotherFile := `This is another file`
+
+	setBackend(t, &backend.MockBackend{
+		SupportsTemplatesF: func() bool { return true },
+		CurrentUserF: func() (string, []string, *workspace.TokenInformation, error) {
+			return "doe", []string{"org1"}, &workspace.TokenInformation{}, nil
+		},
+		ListTemplatesF: func(_ context.Context, orgName string) (apitype.ListOrgTemplatesResponse, error) {
+			assert.Equal(t, "org1", orgName)
+
+			return apitype.ListOrgTemplatesResponse{
+				Templates: map[string][]*apitype.PulumiTemplateRemote{
+					"source1": {template1},
+				},
+				OrgHasTemplates: true,
+			}, nil
+		},
+		DownloadTemplateF: func(_ context.Context, orgName, templateSource string) (backend.TarReaderCloser, error) {
+			assert.Equal(t, "org1", orgName)
+			assert.Equal(t, template1.TemplateURL, templateSource)
+
+			return backend.MockTarReader{
+				"Pulumi.yaml": backend.MockTarFile{Content: pulumiYAML},
+				"other.txt":   backend.MockTarFile{Content: anotherFile},
+			}, nil
+		},
+	})
+
+	source := newImpl(ctx, "name1",
+		ScopeAll, workspace.TemplateKindPulumiProject,
+		false, templateRepository(workspace.TemplateRepository{}, workspace.TemplateNotFoundError{}),
+	)
+
+	template, err := source.Templates()
+	require.NoError(t, err)
+	assert.Equal(t,
+		[]Template{orgTemplate{t: template1, org: "org1", source: source, backend: cmdBackend.BackendInstance}},
+		template)
+	t.Cleanup(func() {
+		assert.NoError(t, source.Close())
+	})
+
+	wTemplate, err := template[0].Download(ctx)
+	require.NoError(t, err)
+
+	{ // Pulumi.yaml
+		file, err := os.ReadFile(filepath.Join(wTemplate.Dir, "Pulumi.yaml"))
+		require.NoError(t, err)
+		assert.Equal(t, pulumiYAML, string(file))
+	}
+	{ // other.txt
+		file, err := os.ReadFile(filepath.Join(wTemplate.Dir, "other.txt"))
+		require.NoError(t, err)
+		assert.Equal(t, anotherFile, string(file))
+	}
+}
+
+func templateRepository(repo workspace.TemplateRepository, err error) getWorkspaceTemplateFunc {
+	return func(ctx context.Context, templateNamePathOrURL string, offline bool,
+		templateKind workspace.TemplateKind,
+	) (workspace.TemplateRepository, error) {
+		return repo, err
+	}
+}
+
+func setBackend(t *testing.T, backend backend.Backend) {
+	oldBackend := cmdBackend.BackendInstance
+	cmdBackend.BackendInstance = backend
+	t.Cleanup(func() { cmdBackend.BackendInstance = oldBackend })
+}
+
+func testContext(t *testing.T) context.Context {
+	ctx := context.Background()
+	ctx, cancel := context.WithCancel(ctx)
+	t.Cleanup(cancel)
+	return ctx
+}
diff --git a/pkg/cmd/pulumi/templates/workspace.go b/pkg/cmd/pulumi/templates/workspace.go
new file mode 100644
index 000000000000..e464704cf68e
--- /dev/null
+++ b/pkg/cmd/pulumi/templates/workspace.go
@@ -0,0 +1,121 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package templates
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"net/http"
+	"net/url"
+	"sync"
+
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+type getWorkspaceTemplateFunc = func(ctx context.Context, templateNamePathOrURL string, offline bool,
+	templateKind workspace.TemplateKind,
+) (workspace.TemplateRepository, error)
+
+func (s *Source) getWorkspaceTemplates(
+	ctx context.Context, templateNamePathOrURL string, scope SearchScope, templateKind workspace.TemplateKind,
+	_ *sync.WaitGroup,
+	get getWorkspaceTemplateFunc,
+) {
+	repo, err := get(ctx, templateNamePathOrURL, scope == ScopeLocal, templateKind)
+	if err != nil {
+		if notFound := (workspace.TemplateNotFoundError{}); errors.As(err, &notFound) {
+			s.addErrorOnEmpty(notFound)
+			return
+		}
+		// Bail on all errors unless its a 401 from a Pulumi Cloud backend...
+		if !errors.Is(err, workspace.ErrPulumiCloudUnauthorized) {
+			s.addError(err)
+			return
+		}
+
+		// ...If the request has 401'd AND we've identified the backend as being a Pulumi Cloud instance, we can
+		// attempt to retrieve the template using the user's Pulumi Cloud credentials.
+		repo, err = retrievePrivatePulumiCloudTemplate(templateNamePathOrURL)
+		if err != nil {
+			s.addError(err)
+			return
+		}
+	}
+	s.addCloser(repo.Delete)
+	workspaceTemplates, err := repo.Templates()
+	if err != nil {
+		s.addError(fmt.Errorf("could not get template from workspace: %w", err))
+		return
+	}
+
+	s.addDownloadedTemplates(workspaceTemplates)
+}
+
+// Retrieve a Private template from the given Pulumi Cloud URL **including an auth token for Pulumi Cloud**.
+func retrievePrivatePulumiCloudTemplate(templateURL string) (workspace.TemplateRepository, error) {
+	u, err := url.Parse(templateURL)
+	if err != nil {
+		return workspace.TemplateRepository{}, fmt.Errorf("parsing template URL: %w", err)
+	}
+	// Docs convention is to store the cloud URL with the protocol.
+	// e.g. `pulumi login https://api.pulumi.com` or `pulumi login https://api.acme.org`
+	templatePulumiCloudHost := "https://" + u.Host
+
+	account, err := workspace.GetAccount(templatePulumiCloudHost)
+	if err != nil {
+		return workspace.TemplateRepository{}, fmt.Errorf(
+			"looking up pulumi cloud backend %s: %w",
+			templatePulumiCloudHost,
+			err,
+		)
+	}
+
+	if account.AccessToken == "" {
+		return workspace.TemplateRepository{}, fmt.Errorf("no access token found for %s", templatePulumiCloudHost)
+	}
+
+	templateRepository, err := workspace.RetrieveZIPTemplates(templateURL, func(req *http.Request) {
+		req.Header.Set("Authorization", "token "+account.AccessToken)
+	})
+
+	if errors.Is(err, workspace.ErrPulumiCloudUnauthorized) {
+		return workspace.TemplateRepository{}, fmt.Errorf(
+			"unauthorized to access template at %s. You may not have access to this template or token may have expired",
+			templatePulumiCloudHost,
+		)
+	}
+
+	// Caller can handle other errors
+	return templateRepository, err
+}
+
+func (s *Source) addDownloadedTemplates(src []workspace.Template) {
+	for _, t := range src {
+		s.addTemplate(workspaceTemplate{t})
+	}
+}
+
+var _ Template = workspaceTemplate{}
+
+type workspaceTemplate struct {
+	t workspace.Template
+}
+
+func (t workspaceTemplate) Name() string                                             { return t.t.Name }
+func (t workspaceTemplate) Description() string                                      { return t.t.Description }
+func (t workspaceTemplate) ProjectDescription() string                               { return t.t.ProjectDescription }
+func (t workspaceTemplate) Error() error                                             { return t.t.Error }
+func (t workspaceTemplate) Download(ctx context.Context) (workspace.Template, error) { return t.t, nil }


Reviews:
user: login: Frassle
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T08:34:09Z
user: login: iwahbe
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T08:59:28Z
user: login: Frassle
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T09:05:32Z
user: login: tgummerer
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-21T09:22:49Z
user: login: iwahbe
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T09:53:44Z
user: login: iwahbe
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T10:06:32Z
user: login: iwahbe
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T10:15:03Z
user: login: iwahbe
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T10:20:21Z
user: login: iwahbe
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T10:34:55Z
user: login: iwahbe
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T10:40:34Z
user: login: iwahbe
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T10:45:54Z
user: login: iwahbe
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T10:47:16Z
user: login: iwahbe
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T10:49:04Z
user: login: iwahbe
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T10:52:47Z
user: login: iwahbe
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T11:01:14Z
user: login: tgummerer
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-24T10:30:32Z
user: login: tgummerer
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-24T10:42:29Z
user: login: iwahbe
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-24T15:32:21Z
user: login: tgummerer
body: Thanks for addressing all those comments! :shipit: 
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-24T16:00:05Z

Review comments:
diff_hunk: @@ -105,6 +105,9 @@ var FallbackToStateSecretsManager = env.Bool("FALLBACK_TO_STATE_SECRETS_MANAGER"
 var Parallel = env.Int("PARALLEL",
 	"Allow P resource operations to run in parallel at once (1 for no parallelism)")
 
+var AccessToken = env.String("ACCESS_TOKEN",
path: sdk/go/common/env/env.go
user: login: Frassle
body: This could be added and normalise usage in it's own PR couldn't it? 
created_at: 2025-03-06T19:17:35Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,227 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package templates adds an abstraction for project templates that may be local or
+// remote.
+//
+// All templates are convertible into [workspace.Template].
+package templates
+
+import (
+	"context"
+	"errors"
+	"io/fs"
+	"os"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+// Source provides access to a set of project templates, any set of which may be present on
+// disk.
+//
+// Source is responsible for cleaning up old templates, and should always be [Close]d when
+// created.
+type Source struct {
+	templates    []Template
+	errorOnEmpty []error
+	errors       []error
+	closers      []func() error
+	closed       bool
+
+	// m should be held whenever Source is mutated.
+	m  sync.Mutex
+	wg sync.WaitGroup
+}
+
+// Templates lists the templates available to the [Source].
+func (s *Source) Templates() ([]Template, error) {
+	s.wg.Wait() // Wait to ensure that all templates have been fetched before returning the template list.
+
+	s.lockOpen("read templates")
+	defer s.m.Unlock()
+	if err := errors.Join(s.errors...); err != nil {
+		return nil, err
+	}
+	if len(s.templates) == 0 {
+		return nil, errors.Join(s.errorOnEmpty...)
+	}
+	return s.templates, nil
+}
+
+func (s *Source) addTemplate(t Template) {
+	s.lockOpen("add template")
+	s.templates = append(s.templates, t)
+	s.m.Unlock()
+}
+
+func (s *Source) addCloser(f func() error) {
+	s.lockOpen("add closer")
+	s.closers = append(s.closers, f)
+	s.m.Unlock()
+}
+
+func (s *Source) addError(err error) {
+	s.lockOpen("add error")
+	s.errors = append(s.errors, err)
+	s.m.Unlock()
+}
+
+func (s *Source) addErrorOnEmpty(err error) {
+	s.lockOpen("add error on empty")
+	s.errorOnEmpty = append(s.errorOnEmpty, err)
+	s.m.Unlock()
+}
+
+func (s *Source) lockOpen(action string) {
+	s.m.Lock()
+	if s.closed {
+		panic("Attempted to act on closed source: " + action)
+	}
+}
+
+// Close cleans up the [Source] and any associated templates.
+//
+// Close should always be called when [Source] is dropped.
+func (s *Source) Close() error {
+	s.wg.Wait() // Wait to ensure that all templates have been fetched so all closers are visible.
path: pkg/cmd/pulumi/templates/templates.go
user: login: tgummerer
body: Hrm shouldn't calling `source.Close()` call all the cancel functions, so we don't keep downloading all the templates for no reason, then wait for the waitgroup, and then call the rest of the closers that have been added?  Otherwise we potentially wait a long time before this is done.
created_at: 2025-03-21T08:15:42Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,227 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package templates adds an abstraction for project templates that may be local or
+// remote.
+//
+// All templates are convertible into [workspace.Template].
+package templates
path: pkg/cmd/pulumi/templates/templates.go
user: login: tgummerer
body: Would be nice to have some unit tests for this and the other new files introduced, by mocking out everything that talks to the internet maybe?
created_at: 2025-03-21T08:18:18Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,227 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package templates adds an abstraction for project templates that may be local or
+// remote.
+//
+// All templates are convertible into [workspace.Template].
+package templates
+
+import (
+	"context"
+	"errors"
+	"io/fs"
+	"os"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+// Source provides access to a set of project templates, any set of which may be present on
+// disk.
+//
+// Source is responsible for cleaning up old templates, and should always be [Close]d when
+// created.
+type Source struct {
+	templates    []Template
+	errorOnEmpty []error
+	errors       []error
+	closers      []func() error
+	closed       bool
+
+	// m should be held whenever Source is mutated.
+	m  sync.Mutex
+	wg sync.WaitGroup
+}
+
+// Templates lists the templates available to the [Source].
+func (s *Source) Templates() ([]Template, error) {
+	s.wg.Wait() // Wait to ensure that all templates have been fetched before returning the template list.
+
+	s.lockOpen("read templates")
+	defer s.m.Unlock()
+	if err := errors.Join(s.errors...); err != nil {
+		return nil, err
+	}
+	if len(s.templates) == 0 {
+		return nil, errors.Join(s.errorOnEmpty...)
+	}
+	return s.templates, nil
+}
+
+func (s *Source) addTemplate(t Template) {
+	s.lockOpen("add template")
+	s.templates = append(s.templates, t)
+	s.m.Unlock()
+}
+
+func (s *Source) addCloser(f func() error) {
+	s.lockOpen("add closer")
+	s.closers = append(s.closers, f)
+	s.m.Unlock()
+}
+
+func (s *Source) addError(err error) {
+	s.lockOpen("add error")
+	s.errors = append(s.errors, err)
+	s.m.Unlock()
+}
+
+func (s *Source) addErrorOnEmpty(err error) {
+	s.lockOpen("add error on empty")
+	s.errorOnEmpty = append(s.errorOnEmpty, err)
+	s.m.Unlock()
+}
+
+func (s *Source) lockOpen(action string) {
+	s.m.Lock()
+	if s.closed {
+		panic("Attempted to act on closed source: " + action)
+	}
+}
+
+// Close cleans up the [Source] and any associated templates.
+//
+// Close should always be called when [Source] is dropped.
+func (s *Source) Close() error {
+	s.wg.Wait() // Wait to ensure that all templates have been fetched so all closers are visible.
+
+	s.lockOpen("close")
+	defer s.m.Unlock()
+	s.closed = true
+	errs := make([]error, len(s.closers))
+	for i, f := range s.closers {
+		errs[i] = f()
+	}
+	return errors.Join(errs...)
+}
+
+type Template struct {
+	name               string
+	description        string
+	projectDescription string
+
+	error error
+
+	download func(ctx context.Context) (workspace.Template, error)
+
+	source *Source
+}
+
+func (t Template) Name() string               { return t.name }
+func (t Template) Description() string        { return t.description }
+func (t Template) ProjectDescription() string { return t.projectDescription }
+func (t Template) Error() error               { return t.error }
+
+func (t Template) Download(ctx context.Context) (workspace.Template, error) {
+	if t.source == nil {
+		panic("Cannot download a template without a host")
+	}
+	if t.source.closed {
+		panic("Cannot download a template from an already closed host")
+	}
+
+	return t.download(ctx)
+}
+
+type Scope struct{ kind string }
path: pkg/cmd/pulumi/templates/templates.go
user: login: Frassle
body: This needs docs. It's not clear what a scope is, or what the choices below each really mean.
created_at: 2025-03-21T08:31:25Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,118 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package templates
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"net/http"
+	"net/url"
+	"sync"
+
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+func (s *Source) getWorkspaceTemplates(
+	ctx context.Context, templateNamePathOrURL string, scope Scope, templateKind workspace.TemplateKind,
+	_ *sync.WaitGroup,
+) {
+	repo, err := workspace.RetrieveTemplates(ctx, templateNamePathOrURL, scope == ScopeLocal, templateKind)
+	if err != nil {
+		if notFound := new(workspace.TemplateNotFoundError); errors.As(err, notFound) {
+			s.addErrorOnEmpty(notFound)
+			return
+		}
+		// Bail on all errors unless its a 401 from a Pulumi Cloud backend...
+		if !errors.Is(err, workspace.ErrPulumiCloudUnauthorized) {
+			s.addError(err)
+			return
+		}
+
+		// ...If the request has 401'd AND we've identified the backend as being a Pulumi Cloud instance, we can
+		// attempt to retrieve the template using the user's Pulumi Cloud credentials.
+		repo, err = retrievePrivatePulumiCloudTemplate(templateNamePathOrURL)
+		if err != nil {
+			s.addError(err)
+			return
+		}
+	}
+	s.addCloser(repo.Delete)
+	workspaceTemplates, err := repo.Templates()
+	if err != nil {
+		s.addError(err)
+		return
+	}
+
+	s.addDownloadedTemplates(workspaceTemplates)
+}
+
+// Retrieve a Private template from the given Pulumi Cloud URL **including an auth token for Pulumi Cloud**.
+//
+// workspace.GetAccount ensures the user has a valid session with the Pulumi Cloud backend.
+//   - If the user is not logged in, the login flow will be initiated.
path: pkg/cmd/pulumi/templates/workspace.go
user: login: Frassle
body: GetAccount doesn't trigger login flow, it just reads from the credential file. All the login logic lives in pkg, if you need access to that here we're gonna have to do some significant refactoring.
created_at: 2025-03-21T08:33:57Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,221 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package templates
+
+import (
+	"archive/tar"
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"io/fs"
+	"math"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/pkg/v3/backend"
+	"github.com/pulumi/pulumi/pkg/v3/backend/display"
+	cmdBackend "github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/backend"
+	pkgWorkspace "github.com/pulumi/pulumi/pkg/v3/workspace"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+func (s *Source) getOrgTemplates(
+	ctx context.Context, templateName string,
+	interactive bool, wg *sync.WaitGroup,
+) {
+	ws := pkgWorkspace.Instance
+	project, _, err := ws.ReadProject()
+	if err != nil && !errors.Is(err, workspace.ErrProjectNotFound) {
+		s.addError(err)
+		return
+	}
+
+	b, err := cmdBackend.CurrentBackend(ctx, ws, cmdBackend.DefaultLoginManager, project, display.Options{
+		Color:         cmdutil.GetGlobalColorization(),
+		IsInteractive: interactive,
+	})
+	if err != nil {
+		if !errors.Is(err, backend.MissingEnvVarForNonInteractiveError{}) {
+			s.addError(err)
+		}
+		return
+	}
+	if !b.SupportsTemplates() {
+		return
+	}
+
+	logging.Infof("Listing Org Templates from the cloud")
path: pkg/cmd/pulumi/templates/org.go
user: login: tgummerer
body: Should this be a debug log?  E.g. if we don't have a current user we don't end up listing anything.  Either we log something there as well, or we remove this log line. otherwise it's kinda confusing if there are no templates found.
created_at: 2025-03-21T08:56:28Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,227 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package templates adds an abstraction for project templates that may be local or
+// remote.
+//
+// All templates are convertible into [workspace.Template].
+package templates
+
+import (
+	"context"
+	"errors"
+	"io/fs"
+	"os"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+// Source provides access to a set of project templates, any set of which may be present on
+// disk.
+//
+// Source is responsible for cleaning up old templates, and should always be [Close]d when
+// created.
+type Source struct {
+	templates    []Template
+	errorOnEmpty []error
+	errors       []error
+	closers      []func() error
+	closed       bool
+
+	// m should be held whenever Source is mutated.
+	m  sync.Mutex
+	wg sync.WaitGroup
+}
+
+// Templates lists the templates available to the [Source].
+func (s *Source) Templates() ([]Template, error) {
+	s.wg.Wait() // Wait to ensure that all templates have been fetched before returning the template list.
+
+	s.lockOpen("read templates")
+	defer s.m.Unlock()
+	if err := errors.Join(s.errors...); err != nil {
+		return nil, err
+	}
+	if len(s.templates) == 0 {
+		return nil, errors.Join(s.errorOnEmpty...)
+	}
+	return s.templates, nil
+}
+
+func (s *Source) addTemplate(t Template) {
+	s.lockOpen("add template")
+	s.templates = append(s.templates, t)
+	s.m.Unlock()
+}
+
+func (s *Source) addCloser(f func() error) {
+	s.lockOpen("add closer")
+	s.closers = append(s.closers, f)
+	s.m.Unlock()
+}
+
+func (s *Source) addError(err error) {
+	s.lockOpen("add error")
+	s.errors = append(s.errors, err)
+	s.m.Unlock()
+}
+
+func (s *Source) addErrorOnEmpty(err error) {
+	s.lockOpen("add error on empty")
+	s.errorOnEmpty = append(s.errorOnEmpty, err)
+	s.m.Unlock()
+}
+
+func (s *Source) lockOpen(action string) {
+	s.m.Lock()
+	if s.closed {
+		panic("Attempted to act on closed source: " + action)
+	}
+}
+
+// Close cleans up the [Source] and any associated templates.
+//
+// Close should always be called when [Source] is dropped.
+func (s *Source) Close() error {
+	s.wg.Wait() // Wait to ensure that all templates have been fetched so all closers are visible.
+
+	s.lockOpen("close")
+	defer s.m.Unlock()
+	s.closed = true
+	errs := make([]error, len(s.closers))
+	for i, f := range s.closers {
+		errs[i] = f()
+	}
+	return errors.Join(errs...)
+}
+
+type Template struct {
+	name               string
+	description        string
+	projectDescription string
+
+	error error
+
+	download func(ctx context.Context) (workspace.Template, error)
+
+	source *Source
+}
+
+func (t Template) Name() string               { return t.name }
+func (t Template) Description() string        { return t.description }
+func (t Template) ProjectDescription() string { return t.projectDescription }
+func (t Template) Error() error               { return t.error }
+
+func (t Template) Download(ctx context.Context) (workspace.Template, error) {
+	if t.source == nil {
+		panic("Cannot download a template without a host")
+	}
+	if t.source.closed {
+		panic("Cannot download a template from an already closed host")
+	}
+
+	return t.download(ctx)
+}
+
+type Scope struct{ kind string }
+
+var (
+	ScopeDefault     = Scope{}
+	scopeDefault     = ScopeAll
+	ScopeTraditional = Scope{"traditional"}
+	ScopeLocal       = Scope{"none"}
+	ScopeAll         = Scope{"all"}
+)
path: pkg/cmd/pulumi/templates/templates.go
user: login: tgummerer
body: These should have docstrings.
created_at: 2025-03-21T08:57:54Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,227 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package templates adds an abstraction for project templates that may be local or
+// remote.
+//
+// All templates are convertible into [workspace.Template].
+package templates
+
+import (
+	"context"
+	"errors"
+	"io/fs"
+	"os"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+// Source provides access to a set of project templates, any set of which may be present on
+// disk.
+//
+// Source is responsible for cleaning up old templates, and should always be [Close]d when
+// created.
+type Source struct {
+	templates    []Template
+	errorOnEmpty []error
+	errors       []error
+	closers      []func() error
+	closed       bool
+
+	// m should be held whenever Source is mutated.
+	m  sync.Mutex
+	wg sync.WaitGroup
+}
+
+// Templates lists the templates available to the [Source].
+func (s *Source) Templates() ([]Template, error) {
+	s.wg.Wait() // Wait to ensure that all templates have been fetched before returning the template list.
+
+	s.lockOpen("read templates")
+	defer s.m.Unlock()
+	if err := errors.Join(s.errors...); err != nil {
+		return nil, err
+	}
+	if len(s.templates) == 0 {
+		return nil, errors.Join(s.errorOnEmpty...)
+	}
+	return s.templates, nil
+}
+
+func (s *Source) addTemplate(t Template) {
+	s.lockOpen("add template")
+	s.templates = append(s.templates, t)
+	s.m.Unlock()
+}
+
+func (s *Source) addCloser(f func() error) {
+	s.lockOpen("add closer")
+	s.closers = append(s.closers, f)
+	s.m.Unlock()
+}
+
+func (s *Source) addError(err error) {
+	s.lockOpen("add error")
+	s.errors = append(s.errors, err)
+	s.m.Unlock()
+}
+
+func (s *Source) addErrorOnEmpty(err error) {
+	s.lockOpen("add error on empty")
+	s.errorOnEmpty = append(s.errorOnEmpty, err)
+	s.m.Unlock()
+}
+
+func (s *Source) lockOpen(action string) {
+	s.m.Lock()
+	if s.closed {
+		panic("Attempted to act on closed source: " + action)
+	}
+}
+
+// Close cleans up the [Source] and any associated templates.
+//
+// Close should always be called when [Source] is dropped.
+func (s *Source) Close() error {
+	s.wg.Wait() // Wait to ensure that all templates have been fetched so all closers are visible.
+
+	s.lockOpen("close")
+	defer s.m.Unlock()
+	s.closed = true
+	errs := make([]error, len(s.closers))
+	for i, f := range s.closers {
+		errs[i] = f()
+	}
+	return errors.Join(errs...)
+}
+
+type Template struct {
+	name               string
+	description        string
+	projectDescription string
+
+	error error
+
+	download func(ctx context.Context) (workspace.Template, error)
+
+	source *Source
+}
+
+func (t Template) Name() string               { return t.name }
+func (t Template) Description() string        { return t.description }
+func (t Template) ProjectDescription() string { return t.projectDescription }
+func (t Template) Error() error               { return t.error }
+
+func (t Template) Download(ctx context.Context) (workspace.Template, error) {
+	if t.source == nil {
+		panic("Cannot download a template without a host")
+	}
+	if t.source.closed {
+		panic("Cannot download a template from an already closed host")
+	}
path: pkg/cmd/pulumi/templates/templates.go
user: login: tgummerer
body: These should probably be `contract.Assertf`s instead.
created_at: 2025-03-21T08:58:37Z
author_association: COLLABORATOR
diff_hunk: @@ -105,6 +105,9 @@ var FallbackToStateSecretsManager = env.Bool("FALLBACK_TO_STATE_SECRETS_MANAGER"
 var Parallel = env.Int("PARALLEL",
 	"Allow P resource operations to run in parallel at once (1 for no parallelism)")
 
+var AccessToken = env.String("ACCESS_TOKEN",
path: sdk/go/common/env/env.go
user: login: iwahbe
body: Yes, and it was: https://github.com/pulumi/pulumi/pull/18822.
created_at: 2025-03-21T08:59:28Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,227 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package templates adds an abstraction for project templates that may be local or
+// remote.
+//
+// All templates are convertible into [workspace.Template].
+package templates
+
+import (
+	"context"
+	"errors"
+	"io/fs"
+	"os"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+// Source provides access to a set of project templates, any set of which may be present on
+// disk.
+//
+// Source is responsible for cleaning up old templates, and should always be [Close]d when
+// created.
+type Source struct {
+	templates    []Template
+	errorOnEmpty []error
+	errors       []error
+	closers      []func() error
+	closed       bool
+
+	// m should be held whenever Source is mutated.
+	m  sync.Mutex
+	wg sync.WaitGroup
+}
+
+// Templates lists the templates available to the [Source].
+func (s *Source) Templates() ([]Template, error) {
+	s.wg.Wait() // Wait to ensure that all templates have been fetched before returning the template list.
+
+	s.lockOpen("read templates")
+	defer s.m.Unlock()
+	if err := errors.Join(s.errors...); err != nil {
+		return nil, err
+	}
+	if len(s.templates) == 0 {
+		return nil, errors.Join(s.errorOnEmpty...)
+	}
+	return s.templates, nil
+}
+
+func (s *Source) addTemplate(t Template) {
+	s.lockOpen("add template")
+	s.templates = append(s.templates, t)
+	s.m.Unlock()
+}
+
+func (s *Source) addCloser(f func() error) {
+	s.lockOpen("add closer")
+	s.closers = append(s.closers, f)
+	s.m.Unlock()
+}
+
+func (s *Source) addError(err error) {
+	s.lockOpen("add error")
+	s.errors = append(s.errors, err)
+	s.m.Unlock()
+}
+
+func (s *Source) addErrorOnEmpty(err error) {
+	s.lockOpen("add error on empty")
+	s.errorOnEmpty = append(s.errorOnEmpty, err)
+	s.m.Unlock()
+}
+
+func (s *Source) lockOpen(action string) {
+	s.m.Lock()
+	if s.closed {
+		panic("Attempted to act on closed source: " + action)
+	}
+}
+
+// Close cleans up the [Source] and any associated templates.
+//
+// Close should always be called when [Source] is dropped.
+func (s *Source) Close() error {
+	s.wg.Wait() // Wait to ensure that all templates have been fetched so all closers are visible.
+
+	s.lockOpen("close")
+	defer s.m.Unlock()
+	s.closed = true
+	errs := make([]error, len(s.closers))
+	for i, f := range s.closers {
+		errs[i] = f()
+	}
+	return errors.Join(errs...)
+}
+
+type Template struct {
+	name               string
+	description        string
+	projectDescription string
+
+	error error
+
+	download func(ctx context.Context) (workspace.Template, error)
+
+	source *Source
+}
+
+func (t Template) Name() string               { return t.name }
+func (t Template) Description() string        { return t.description }
+func (t Template) ProjectDescription() string { return t.projectDescription }
+func (t Template) Error() error               { return t.error }
+
+func (t Template) Download(ctx context.Context) (workspace.Template, error) {
+	if t.source == nil {
+		panic("Cannot download a template without a host")
+	}
+	if t.source.closed {
+		panic("Cannot download a template from an already closed host")
+	}
+
+	return t.download(ctx)
+}
+
+type Scope struct{ kind string }
+
+var (
+	ScopeDefault     = Scope{}
+	scopeDefault     = ScopeAll
+	ScopeTraditional = Scope{"traditional"}
+	ScopeLocal       = Scope{"none"}
+	ScopeAll         = Scope{"all"}
+)
+
+// Create a new [Template] [Source] associated with a given [Scope].
+func New(
+	ctx context.Context, templateNamePathOrURL string, scope Scope,
+	templateKind workspace.TemplateKind, interactive bool,
+) *Source {
+	// apply the default scope, if necessary
+	if scope == ScopeDefault {
+		scope = scopeDefault
+	}
+
+	var source Source
+	ctx, cancel := context.WithCancel(ctx)
+	source.closers = append(source.closers, func() error { cancel(); return nil })
+
+	queryKind := getTemplateQuery(templateNamePathOrURL)
+
+	if scope == ScopeAll || scope == ScopeTraditional || scope == ScopeLocal {
+		source.wg.Add(1)
+		go func() {
+			source.getWorkspaceTemplates(ctx, templateNamePathOrURL, scope, templateKind, &source.wg)
+			source.wg.Done()
+		}()
+	}
+
+	if scope == ScopeAll && templateKind == workspace.TemplateKindPulumiProject && queryKind == queryName {
+		source.wg.Add(1)
+		go func() {
+			source.getOrgTemplates(ctx, templateNamePathOrURL, interactive, &source.wg)
+			source.wg.Done()
+		}()
+	}
+
+	return &source
+}
+
+type queryKind string
+
+const (
+	queryName queryKind = "query-name"
+	queryURL  queryKind = "query-url"
+	queryPath queryKind = "query-path"
+)
path: pkg/cmd/pulumi/templates/templates.go
user: login: tgummerer
body: some docstrings with an explanation why this is here and what it is doing would be helpful.
created_at: 2025-03-21T09:02:12Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,227 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package templates adds an abstraction for project templates that may be local or
+// remote.
+//
+// All templates are convertible into [workspace.Template].
+package templates
+
+import (
+	"context"
+	"errors"
+	"io/fs"
+	"os"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+// Source provides access to a set of project templates, any set of which may be present on
+// disk.
+//
+// Source is responsible for cleaning up old templates, and should always be [Close]d when
+// created.
+type Source struct {
+	templates    []Template
+	errorOnEmpty []error
+	errors       []error
+	closers      []func() error
+	closed       bool
+
+	// m should be held whenever Source is mutated.
+	m  sync.Mutex
+	wg sync.WaitGroup
+}
+
+// Templates lists the templates available to the [Source].
+func (s *Source) Templates() ([]Template, error) {
+	s.wg.Wait() // Wait to ensure that all templates have been fetched before returning the template list.
+
+	s.lockOpen("read templates")
+	defer s.m.Unlock()
+	if err := errors.Join(s.errors...); err != nil {
+		return nil, err
+	}
+	if len(s.templates) == 0 {
+		return nil, errors.Join(s.errorOnEmpty...)
+	}
+	return s.templates, nil
+}
+
+func (s *Source) addTemplate(t Template) {
+	s.lockOpen("add template")
+	s.templates = append(s.templates, t)
+	s.m.Unlock()
+}
+
+func (s *Source) addCloser(f func() error) {
+	s.lockOpen("add closer")
+	s.closers = append(s.closers, f)
+	s.m.Unlock()
+}
+
+func (s *Source) addError(err error) {
+	s.lockOpen("add error")
+	s.errors = append(s.errors, err)
+	s.m.Unlock()
+}
+
+func (s *Source) addErrorOnEmpty(err error) {
+	s.lockOpen("add error on empty")
+	s.errorOnEmpty = append(s.errorOnEmpty, err)
+	s.m.Unlock()
+}
+
+func (s *Source) lockOpen(action string) {
+	s.m.Lock()
+	if s.closed {
+		panic("Attempted to act on closed source: " + action)
+	}
+}
+
+// Close cleans up the [Source] and any associated templates.
+//
+// Close should always be called when [Source] is dropped.
+func (s *Source) Close() error {
+	s.wg.Wait() // Wait to ensure that all templates have been fetched so all closers are visible.
+
+	s.lockOpen("close")
+	defer s.m.Unlock()
+	s.closed = true
+	errs := make([]error, len(s.closers))
+	for i, f := range s.closers {
+		errs[i] = f()
+	}
+	return errors.Join(errs...)
+}
+
+type Template struct {
+	name               string
+	description        string
+	projectDescription string
+
+	error error
+
+	download func(ctx context.Context) (workspace.Template, error)
+
+	source *Source
+}
+
+func (t Template) Name() string               { return t.name }
+func (t Template) Description() string        { return t.description }
+func (t Template) ProjectDescription() string { return t.projectDescription }
+func (t Template) Error() error               { return t.error }
+
+func (t Template) Download(ctx context.Context) (workspace.Template, error) {
+	if t.source == nil {
+		panic("Cannot download a template without a host")
+	}
+	if t.source.closed {
+		panic("Cannot download a template from an already closed host")
+	}
+
+	return t.download(ctx)
+}
+
+type Scope struct{ kind string }
+
+var (
+	ScopeDefault     = Scope{}
+	scopeDefault     = ScopeAll
+	ScopeTraditional = Scope{"traditional"}
+	ScopeLocal       = Scope{"none"}
+	ScopeAll         = Scope{"all"}
+)
+
+// Create a new [Template] [Source] associated with a given [Scope].
+func New(
+	ctx context.Context, templateNamePathOrURL string, scope Scope,
+	templateKind workspace.TemplateKind, interactive bool,
+) *Source {
+	// apply the default scope, if necessary
+	if scope == ScopeDefault {
+		scope = scopeDefault
+	}
+
+	var source Source
+	ctx, cancel := context.WithCancel(ctx)
+	source.closers = append(source.closers, func() error { cancel(); return nil })
+
+	queryKind := getTemplateQuery(templateNamePathOrURL)
+
+	if scope == ScopeAll || scope == ScopeTraditional || scope == ScopeLocal {
+		source.wg.Add(1)
+		go func() {
+			source.getWorkspaceTemplates(ctx, templateNamePathOrURL, scope, templateKind, &source.wg)
+			source.wg.Done()
+		}()
+	}
+
+	if scope == ScopeAll && templateKind == workspace.TemplateKindPulumiProject && queryKind == queryName {
path: pkg/cmd/pulumi/templates/templates.go
user: login: tgummerer
body: Instead of `queryKind == queryName` here, which I stumbled over for a while, could we just have a function `isTemplateName(templateNamePathOrURL)`?  I think that would be less confusing than `queryKind` here.
created_at: 2025-03-21T09:03:45Z
author_association: COLLABORATOR
diff_hunk: @@ -105,6 +105,9 @@ var FallbackToStateSecretsManager = env.Bool("FALLBACK_TO_STATE_SECRETS_MANAGER"
 var Parallel = env.Int("PARALLEL",
 	"Allow P resource operations to run in parallel at once (1 for no parallelism)")
 
+var AccessToken = env.String("ACCESS_TOKEN",
path: sdk/go/common/env/env.go
user: login: Frassle
body: That comment is from 2 weeks ago :) 
created_at: 2025-03-21T09:05:31Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,221 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package templates
+
+import (
+	"archive/tar"
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"io/fs"
+	"math"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/pkg/v3/backend"
+	"github.com/pulumi/pulumi/pkg/v3/backend/display"
+	cmdBackend "github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/backend"
+	pkgWorkspace "github.com/pulumi/pulumi/pkg/v3/workspace"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+func (s *Source) getOrgTemplates(
+	ctx context.Context, templateName string,
+	interactive bool, wg *sync.WaitGroup,
+) {
+	ws := pkgWorkspace.Instance
+	project, _, err := ws.ReadProject()
+	if err != nil && !errors.Is(err, workspace.ErrProjectNotFound) {
+		s.addError(err)
+		return
+	}
+
+	b, err := cmdBackend.CurrentBackend(ctx, ws, cmdBackend.DefaultLoginManager, project, display.Options{
+		Color:         cmdutil.GetGlobalColorization(),
+		IsInteractive: interactive,
+	})
+	if err != nil {
+		if !errors.Is(err, backend.MissingEnvVarForNonInteractiveError{}) {
+			s.addError(err)
+		}
+		return
+	}
+	if !b.SupportsTemplates() {
+		return
+	}
+
+	logging.Infof("Listing Org Templates from the cloud")
+	user, orgs, _, err := b.CurrentUser()
+	if err != nil {
+		s.addError(err)
+		return
+	} else if user == "" {
+		return // No current user - so don't proceed.
+	}
+
+	alreadySeenSourceURLs := map[string]struct{}{}
+
+	handleOrg := func(org string) {
+		defer wg.Done()
+		logging.Infof("Checking for templates from %q", org)
+		orgTemplates, err := b.ListTemplates(ctx, org)
+		if apiError := new(apitype.ErrorResponse); errors.As(err, &apiError) {
+			// This is what happens when we try to access org templates for an org that hasn't enabled org templates.
+			if apiError.Code == 402 {
+				logging.Infof("%q does not have access to org templates (code=%d)", org, apiError.Code)
path: pkg/cmd/pulumi/templates/org.go
user: login: tgummerer
body: If they don't have access to org templates shouldn't we just silently not use them?  This doesn't strike me as a great user experience for those not having access to them.  This is especially true if the user is part of multiple orgs and only one of them supports templates.
created_at: 2025-03-21T09:08:58Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,221 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package templates
+
+import (
+	"archive/tar"
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"io/fs"
+	"math"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/pkg/v3/backend"
+	"github.com/pulumi/pulumi/pkg/v3/backend/display"
+	cmdBackend "github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/backend"
+	pkgWorkspace "github.com/pulumi/pulumi/pkg/v3/workspace"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+func (s *Source) getOrgTemplates(
+	ctx context.Context, templateName string,
+	interactive bool, wg *sync.WaitGroup,
+) {
+	ws := pkgWorkspace.Instance
+	project, _, err := ws.ReadProject()
+	if err != nil && !errors.Is(err, workspace.ErrProjectNotFound) {
+		s.addError(err)
+		return
+	}
+
+	b, err := cmdBackend.CurrentBackend(ctx, ws, cmdBackend.DefaultLoginManager, project, display.Options{
+		Color:         cmdutil.GetGlobalColorization(),
+		IsInteractive: interactive,
+	})
+	if err != nil {
+		if !errors.Is(err, backend.MissingEnvVarForNonInteractiveError{}) {
+			s.addError(err)
+		}
+		return
+	}
+	if !b.SupportsTemplates() {
+		return
+	}
+
+	logging.Infof("Listing Org Templates from the cloud")
+	user, orgs, _, err := b.CurrentUser()
+	if err != nil {
+		s.addError(err)
+		return
+	} else if user == "" {
+		return // No current user - so don't proceed.
+	}
+
+	alreadySeenSourceURLs := map[string]struct{}{}
+
+	handleOrg := func(org string) {
+		defer wg.Done()
+		logging.Infof("Checking for templates from %q", org)
+		orgTemplates, err := b.ListTemplates(ctx, org)
+		if apiError := new(apitype.ErrorResponse); errors.As(err, &apiError) {
+			// This is what happens when we try to access org templates for an org that hasn't enabled org templates.
+			if apiError.Code == 402 {
+				logging.Infof("%q does not have access to org templates (code=%d)", org, apiError.Code)
+				return
+			}
+		} else if err != nil {
+			s.addError(err)
+			logging.Warningf("Failed to get templates from %q: %s", org, err.Error())
+			return
+		} else if orgTemplates.HasAccessError {
+			logging.Warningf("Failed to get templates from %q: Access Denied", org)
+			return
+		} else if orgTemplates.HasUpstreamError {
+			logging.Warningf("Failed to get templates from %q: Upstream Error", org)
+			return
path: pkg/cmd/pulumi/templates/org.go
user: login: tgummerer
body: Both of these errors could use a little more explanation why this is happening imo.
created_at: 2025-03-21T09:09:41Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,221 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package templates
+
+import (
+	"archive/tar"
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"io/fs"
+	"math"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/pkg/v3/backend"
+	"github.com/pulumi/pulumi/pkg/v3/backend/display"
+	cmdBackend "github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/backend"
+	pkgWorkspace "github.com/pulumi/pulumi/pkg/v3/workspace"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+func (s *Source) getOrgTemplates(
+	ctx context.Context, templateName string,
+	interactive bool, wg *sync.WaitGroup,
+) {
+	ws := pkgWorkspace.Instance
+	project, _, err := ws.ReadProject()
+	if err != nil && !errors.Is(err, workspace.ErrProjectNotFound) {
+		s.addError(err)
+		return
+	}
+
+	b, err := cmdBackend.CurrentBackend(ctx, ws, cmdBackend.DefaultLoginManager, project, display.Options{
+		Color:         cmdutil.GetGlobalColorization(),
+		IsInteractive: interactive,
+	})
+	if err != nil {
+		if !errors.Is(err, backend.MissingEnvVarForNonInteractiveError{}) {
+			s.addError(err)
+		}
+		return
+	}
+	if !b.SupportsTemplates() {
+		return
+	}
+
+	logging.Infof("Listing Org Templates from the cloud")
+	user, orgs, _, err := b.CurrentUser()
+	if err != nil {
+		s.addError(err)
+		return
+	} else if user == "" {
+		return // No current user - so don't proceed.
+	}
+
+	alreadySeenSourceURLs := map[string]struct{}{}
+
+	handleOrg := func(org string) {
+		defer wg.Done()
+		logging.Infof("Checking for templates from %q", org)
+		orgTemplates, err := b.ListTemplates(ctx, org)
+		if apiError := new(apitype.ErrorResponse); errors.As(err, &apiError) {
+			// This is what happens when we try to access org templates for an org that hasn't enabled org templates.
+			if apiError.Code == 402 {
+				logging.Infof("%q does not have access to org templates (code=%d)", org, apiError.Code)
+				return
+			}
+		} else if err != nil {
+			s.addError(err)
+			logging.Warningf("Failed to get templates from %q: %s", org, err.Error())
+			return
+		} else if orgTemplates.HasAccessError {
+			logging.Warningf("Failed to get templates from %q: Access Denied", org)
+			return
+		} else if orgTemplates.HasUpstreamError {
+			logging.Warningf("Failed to get templates from %q: Upstream Error", org)
+			return
+		}
+
+		for source, sourceTemplates := range orgTemplates.Templates {
+			logging.Infof("sourcing templates from %q", source)
+			for _, template := range sourceTemplates {
+				// These template are maintained using https://github.com/pulumi/templates, and are
+				// ingested without going through the Pulumi Cloud.
+				//
+				//
+				if strings.HasPrefix(template.TemplateURL, "https://github.com/pulumi/templates") {
+					continue
+				}
+
+				// Check if we already have this template from another source.
+				if _, ok := alreadySeenSourceURLs[template.TemplateURL]; ok {
+					// Skip a template that we have already seen.
+					continue
+				}
+				alreadySeenSourceURLs[template.TemplateURL] = struct{}{}
+
+				// If we are searching for a template of a specific name,
+				// only match templates of that name.
+				if templateName != "" && templateName != template.Name {
+					logging.V(10).Infof("skipping template %q", template.Name)
+					continue
+				}
+
+				logging.V(10).Infof("adding template %q", template.Name)
+				s.addTemplate(Template{
+					name:               template.Name,
+					description:        "", // No description present.
path: pkg/cmd/pulumi/templates/org.go
user: login: tgummerer
body: Will org templates never support descriptions?  Otherwise we should probably future proof this.
created_at: 2025-03-21T09:12:27Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,221 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package templates
+
+import (
+	"archive/tar"
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"io/fs"
+	"math"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/pkg/v3/backend"
+	"github.com/pulumi/pulumi/pkg/v3/backend/display"
+	cmdBackend "github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/backend"
+	pkgWorkspace "github.com/pulumi/pulumi/pkg/v3/workspace"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+func (s *Source) getOrgTemplates(
+	ctx context.Context, templateName string,
+	interactive bool, wg *sync.WaitGroup,
+) {
+	ws := pkgWorkspace.Instance
+	project, _, err := ws.ReadProject()
+	if err != nil && !errors.Is(err, workspace.ErrProjectNotFound) {
+		s.addError(err)
+		return
+	}
+
+	b, err := cmdBackend.CurrentBackend(ctx, ws, cmdBackend.DefaultLoginManager, project, display.Options{
+		Color:         cmdutil.GetGlobalColorization(),
+		IsInteractive: interactive,
+	})
+	if err != nil {
+		if !errors.Is(err, backend.MissingEnvVarForNonInteractiveError{}) {
+			s.addError(err)
+		}
+		return
+	}
+	if !b.SupportsTemplates() {
+		return
+	}
+
+	logging.Infof("Listing Org Templates from the cloud")
+	user, orgs, _, err := b.CurrentUser()
+	if err != nil {
+		s.addError(err)
+		return
+	} else if user == "" {
+		return // No current user - so don't proceed.
+	}
+
+	alreadySeenSourceURLs := map[string]struct{}{}
+
+	handleOrg := func(org string) {
+		defer wg.Done()
+		logging.Infof("Checking for templates from %q", org)
+		orgTemplates, err := b.ListTemplates(ctx, org)
+		if apiError := new(apitype.ErrorResponse); errors.As(err, &apiError) {
+			// This is what happens when we try to access org templates for an org that hasn't enabled org templates.
+			if apiError.Code == 402 {
+				logging.Infof("%q does not have access to org templates (code=%d)", org, apiError.Code)
+				return
+			}
+		} else if err != nil {
+			s.addError(err)
+			logging.Warningf("Failed to get templates from %q: %s", org, err.Error())
+			return
+		} else if orgTemplates.HasAccessError {
+			logging.Warningf("Failed to get templates from %q: Access Denied", org)
+			return
+		} else if orgTemplates.HasUpstreamError {
+			logging.Warningf("Failed to get templates from %q: Upstream Error", org)
+			return
+		}
+
+		for source, sourceTemplates := range orgTemplates.Templates {
+			logging.Infof("sourcing templates from %q", source)
+			for _, template := range sourceTemplates {
+				// These template are maintained using https://github.com/pulumi/templates, and are
+				// ingested without going through the Pulumi Cloud.
+				//
+				//
+				if strings.HasPrefix(template.TemplateURL, "https://github.com/pulumi/templates") {
+					continue
+				}
+
+				// Check if we already have this template from another source.
+				if _, ok := alreadySeenSourceURLs[template.TemplateURL]; ok {
+					// Skip a template that we have already seen.
+					continue
+				}
+				alreadySeenSourceURLs[template.TemplateURL] = struct{}{}
+
+				// If we are searching for a template of a specific name,
+				// only match templates of that name.
+				if templateName != "" && templateName != template.Name {
+					logging.V(10).Infof("skipping template %q", template.Name)
+					continue
+				}
+
+				logging.V(10).Infof("adding template %q", template.Name)
+				s.addTemplate(Template{
+					name:               template.Name,
+					description:        "", // No description present.
+					projectDescription: template.Description,
+					source:             s,
+					download: func(ctx context.Context) (workspace.Template, error) {
+						templateDir, err := os.MkdirTemp("", "pulumi-template-")
+						if err != nil {
+							return workspace.Template{}, err
+						}
+						// Having created a template directory, we now add it to the list of directories to close.
+						s.addCloser(func() error { return os.RemoveAll(templateDir) })
+
+						tarReader, err := b.DownloadTemplate(ctx, org, template.TemplateURL)
+						if err != nil {
+							return workspace.Template{}, err
+						}
+						if err := errors.Join(
+							writeTar(ctx, tarReader.Tar(), templateDir),
+							tarReader.Close(),
+						); err != nil {
+							return workspace.Template{}, err
+						}
+						logging.Infof("downloaded %q into %q", template.Name, templateDir)
+
+						return workspace.LoadTemplate(templateDir)
+					},
+				})
+			}
+		}
+	}
+
+	for _, org := range orgs {
+		wg.Add(1)
+		go handleOrg(org)
+	}
+}
+
+func writeTar(_ context.Context, reader *tar.Reader, dst string) error {
path: pkg/cmd/pulumi/templates/org.go
user: login: tgummerer
body: Should we check `context` at each step of the for loop here?  It's pretty cheap to do and `tar` files can have quite horrible characteristics, so being able to abort an unpack might be useful here?
created_at: 2025-03-21T09:18:17Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,221 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package templates
+
+import (
+	"archive/tar"
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"io/fs"
+	"math"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/pkg/v3/backend"
+	"github.com/pulumi/pulumi/pkg/v3/backend/display"
+	cmdBackend "github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/backend"
+	pkgWorkspace "github.com/pulumi/pulumi/pkg/v3/workspace"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+func (s *Source) getOrgTemplates(
+	ctx context.Context, templateName string,
+	interactive bool, wg *sync.WaitGroup,
+) {
+	ws := pkgWorkspace.Instance
+	project, _, err := ws.ReadProject()
+	if err != nil && !errors.Is(err, workspace.ErrProjectNotFound) {
+		s.addError(err)
+		return
+	}
+
+	b, err := cmdBackend.CurrentBackend(ctx, ws, cmdBackend.DefaultLoginManager, project, display.Options{
+		Color:         cmdutil.GetGlobalColorization(),
+		IsInteractive: interactive,
+	})
+	if err != nil {
+		if !errors.Is(err, backend.MissingEnvVarForNonInteractiveError{}) {
+			s.addError(err)
+		}
+		return
+	}
+	if !b.SupportsTemplates() {
+		return
+	}
+
+	logging.Infof("Listing Org Templates from the cloud")
+	user, orgs, _, err := b.CurrentUser()
+	if err != nil {
+		s.addError(err)
+		return
+	} else if user == "" {
+		return // No current user - so don't proceed.
+	}
+
+	alreadySeenSourceURLs := map[string]struct{}{}
+
+	handleOrg := func(org string) {
+		defer wg.Done()
+		logging.Infof("Checking for templates from %q", org)
+		orgTemplates, err := b.ListTemplates(ctx, org)
+		if apiError := new(apitype.ErrorResponse); errors.As(err, &apiError) {
+			// This is what happens when we try to access org templates for an org that hasn't enabled org templates.
+			if apiError.Code == 402 {
+				logging.Infof("%q does not have access to org templates (code=%d)", org, apiError.Code)
+				return
+			}
+		} else if err != nil {
+			s.addError(err)
+			logging.Warningf("Failed to get templates from %q: %s", org, err.Error())
+			return
+		} else if orgTemplates.HasAccessError {
+			logging.Warningf("Failed to get templates from %q: Access Denied", org)
+			return
+		} else if orgTemplates.HasUpstreamError {
+			logging.Warningf("Failed to get templates from %q: Upstream Error", org)
+			return
+		}
+
+		for source, sourceTemplates := range orgTemplates.Templates {
+			logging.Infof("sourcing templates from %q", source)
+			for _, template := range sourceTemplates {
+				// These template are maintained using https://github.com/pulumi/templates, and are
+				// ingested without going through the Pulumi Cloud.
+				//
+				//
+				if strings.HasPrefix(template.TemplateURL, "https://github.com/pulumi/templates") {
+					continue
+				}
+
+				// Check if we already have this template from another source.
+				if _, ok := alreadySeenSourceURLs[template.TemplateURL]; ok {
+					// Skip a template that we have already seen.
+					continue
+				}
+				alreadySeenSourceURLs[template.TemplateURL] = struct{}{}
+
+				// If we are searching for a template of a specific name,
+				// only match templates of that name.
+				if templateName != "" && templateName != template.Name {
+					logging.V(10).Infof("skipping template %q", template.Name)
+					continue
+				}
+
+				logging.V(10).Infof("adding template %q", template.Name)
+				s.addTemplate(Template{
+					name:               template.Name,
+					description:        "", // No description present.
+					projectDescription: template.Description,
+					source:             s,
+					download: func(ctx context.Context) (workspace.Template, error) {
+						templateDir, err := os.MkdirTemp("", "pulumi-template-")
+						if err != nil {
+							return workspace.Template{}, err
+						}
+						// Having created a template directory, we now add it to the list of directories to close.
+						s.addCloser(func() error { return os.RemoveAll(templateDir) })
+
+						tarReader, err := b.DownloadTemplate(ctx, org, template.TemplateURL)
+						if err != nil {
+							return workspace.Template{}, err
+						}
+						if err := errors.Join(
+							writeTar(ctx, tarReader.Tar(), templateDir),
+							tarReader.Close(),
+						); err != nil {
+							return workspace.Template{}, err
+						}
+						logging.Infof("downloaded %q into %q", template.Name, templateDir)
+
+						return workspace.LoadTemplate(templateDir)
+					},
+				})
+			}
+		}
+	}
+
+	for _, org := range orgs {
+		wg.Add(1)
+		go handleOrg(org)
+	}
+}
+
+func writeTar(_ context.Context, reader *tar.Reader, dst string) error {
+	for {
+		header, err := reader.Next()
+		if errors.Is(err, io.EOF) {
+			return nil
+		} else if err != nil {
+			return err
+		}
+
+		logging.V(8).Infof("Decompressing %q", header.Name)
+
+		path := filepath.Clean(header.Name)
+		if !filepath.IsLocal(path) {
+			return fmt.Errorf("refusing to write non-local path %q", path)
+		}
+
+		target := filepath.Join(dst, path)
+
+		// Ensure that we can write the directory
+		if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
+			return err
+		}
+
+		switch header.Typeflag {
+		case tar.TypeDir:
+			if header.Mode > math.MaxUint32 {
+				return fmt.Errorf("invalid file mode for %q: %02x", header.Name, header.Mode)
+			}
+
+			fileMode := os.FileMode(header.Mode) //nolint:gosec // We checked the overflow
+			err := os.Mkdir(target, fileMode)
+			if err != nil && !errors.Is(err, fs.ErrExist) {
+				return err
+			}
+
+		case tar.TypeReg:
+			if header.Mode > math.MaxUint32 {
+				return fmt.Errorf("invalid file mode for %q: %02x", header.Name, header.Mode)
+			}
+
+			fileMode := os.FileMode(header.Mode) //nolint:gosec // We checked the overflow
+			f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, fileMode)
+			if err != nil {
+				return err
+			}
+
+			// Write the tar file into f
+			if _, err := io.Copy(f, reader); err != nil {
+				return errors.Join(err, f.Close())
+			}
+
+			// Close f. We don't use defer because we want to close each file
+			// after we open it, not leave it all until the download function
+			// exits.
+			if err := f.Close(); err != nil {
path: pkg/cmd/pulumi/templates/org.go
user: login: tgummerer
body: This means we don't close it if `io.Copy` fails. You can instead use a function to scope the defer:

```
	func() {
			f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, fileMode)
			if err != nil {
				return err
			}
			defer f.Close()

			// Write the tar file into f
			if _, err := io.Copy(f, reader); err != nil {
				return errors.Join(err, f.Close())
			}
	}()
```
created_at: 2025-03-21T09:22:33Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,221 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package templates
+
+import (
+	"archive/tar"
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"io/fs"
+	"math"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/pkg/v3/backend"
+	"github.com/pulumi/pulumi/pkg/v3/backend/display"
+	cmdBackend "github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/backend"
+	pkgWorkspace "github.com/pulumi/pulumi/pkg/v3/workspace"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+func (s *Source) getOrgTemplates(
+	ctx context.Context, templateName string,
+	interactive bool, wg *sync.WaitGroup,
+) {
+	ws := pkgWorkspace.Instance
+	project, _, err := ws.ReadProject()
+	if err != nil && !errors.Is(err, workspace.ErrProjectNotFound) {
+		s.addError(err)
+		return
+	}
+
+	b, err := cmdBackend.CurrentBackend(ctx, ws, cmdBackend.DefaultLoginManager, project, display.Options{
+		Color:         cmdutil.GetGlobalColorization(),
+		IsInteractive: interactive,
+	})
+	if err != nil {
+		if !errors.Is(err, backend.MissingEnvVarForNonInteractiveError{}) {
+			s.addError(err)
+		}
+		return
+	}
+	if !b.SupportsTemplates() {
+		return
+	}
+
+	logging.Infof("Listing Org Templates from the cloud")
+	user, orgs, _, err := b.CurrentUser()
+	if err != nil {
+		s.addError(err)
+		return
+	} else if user == "" {
+		return // No current user - so don't proceed.
+	}
+
+	alreadySeenSourceURLs := map[string]struct{}{}
+
+	handleOrg := func(org string) {
+		defer wg.Done()
+		logging.Infof("Checking for templates from %q", org)
+		orgTemplates, err := b.ListTemplates(ctx, org)
+		if apiError := new(apitype.ErrorResponse); errors.As(err, &apiError) {
+			// This is what happens when we try to access org templates for an org that hasn't enabled org templates.
+			if apiError.Code == 402 {
+				logging.Infof("%q does not have access to org templates (code=%d)", org, apiError.Code)
+				return
+			}
+		} else if err != nil {
+			s.addError(err)
+			logging.Warningf("Failed to get templates from %q: %s", org, err.Error())
+			return
+		} else if orgTemplates.HasAccessError {
+			logging.Warningf("Failed to get templates from %q: Access Denied", org)
+			return
+		} else if orgTemplates.HasUpstreamError {
+			logging.Warningf("Failed to get templates from %q: Upstream Error", org)
+			return
+		}
+
+		for source, sourceTemplates := range orgTemplates.Templates {
+			logging.Infof("sourcing templates from %q", source)
+			for _, template := range sourceTemplates {
+				// These template are maintained using https://github.com/pulumi/templates, and are
+				// ingested without going through the Pulumi Cloud.
+				//
+				//
+				if strings.HasPrefix(template.TemplateURL, "https://github.com/pulumi/templates") {
+					continue
+				}
+
+				// Check if we already have this template from another source.
+				if _, ok := alreadySeenSourceURLs[template.TemplateURL]; ok {
+					// Skip a template that we have already seen.
+					continue
+				}
+				alreadySeenSourceURLs[template.TemplateURL] = struct{}{}
+
+				// If we are searching for a template of a specific name,
+				// only match templates of that name.
+				if templateName != "" && templateName != template.Name {
+					logging.V(10).Infof("skipping template %q", template.Name)
+					continue
+				}
+
+				logging.V(10).Infof("adding template %q", template.Name)
+				s.addTemplate(Template{
+					name:               template.Name,
+					description:        "", // No description present.
+					projectDescription: template.Description,
+					source:             s,
+					download: func(ctx context.Context) (workspace.Template, error) {
+						templateDir, err := os.MkdirTemp("", "pulumi-template-")
+						if err != nil {
+							return workspace.Template{}, err
+						}
+						// Having created a template directory, we now add it to the list of directories to close.
+						s.addCloser(func() error { return os.RemoveAll(templateDir) })
+
+						tarReader, err := b.DownloadTemplate(ctx, org, template.TemplateURL)
+						if err != nil {
+							return workspace.Template{}, err
+						}
+						if err := errors.Join(
+							writeTar(ctx, tarReader.Tar(), templateDir),
+							tarReader.Close(),
+						); err != nil {
+							return workspace.Template{}, err
+						}
+						logging.Infof("downloaded %q into %q", template.Name, templateDir)
+
+						return workspace.LoadTemplate(templateDir)
+					},
+				})
+			}
+		}
+	}
+
+	for _, org := range orgs {
+		wg.Add(1)
+		go handleOrg(org)
+	}
+}
+
+func writeTar(_ context.Context, reader *tar.Reader, dst string) error {
path: pkg/cmd/pulumi/templates/org.go
user: login: iwahbe
body: Excelent idea.
created_at: 2025-03-21T09:53:44Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,221 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package templates
+
+import (
+	"archive/tar"
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"io/fs"
+	"math"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/pkg/v3/backend"
+	"github.com/pulumi/pulumi/pkg/v3/backend/display"
+	cmdBackend "github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/backend"
+	pkgWorkspace "github.com/pulumi/pulumi/pkg/v3/workspace"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+func (s *Source) getOrgTemplates(
+	ctx context.Context, templateName string,
+	interactive bool, wg *sync.WaitGroup,
+) {
+	ws := pkgWorkspace.Instance
+	project, _, err := ws.ReadProject()
+	if err != nil && !errors.Is(err, workspace.ErrProjectNotFound) {
+		s.addError(err)
+		return
+	}
+
+	b, err := cmdBackend.CurrentBackend(ctx, ws, cmdBackend.DefaultLoginManager, project, display.Options{
+		Color:         cmdutil.GetGlobalColorization(),
+		IsInteractive: interactive,
+	})
+	if err != nil {
+		if !errors.Is(err, backend.MissingEnvVarForNonInteractiveError{}) {
+			s.addError(err)
+		}
+		return
+	}
+	if !b.SupportsTemplates() {
+		return
+	}
+
+	logging.Infof("Listing Org Templates from the cloud")
+	user, orgs, _, err := b.CurrentUser()
+	if err != nil {
+		s.addError(err)
+		return
+	} else if user == "" {
+		return // No current user - so don't proceed.
+	}
+
+	alreadySeenSourceURLs := map[string]struct{}{}
+
+	handleOrg := func(org string) {
+		defer wg.Done()
+		logging.Infof("Checking for templates from %q", org)
+		orgTemplates, err := b.ListTemplates(ctx, org)
+		if apiError := new(apitype.ErrorResponse); errors.As(err, &apiError) {
+			// This is what happens when we try to access org templates for an org that hasn't enabled org templates.
+			if apiError.Code == 402 {
+				logging.Infof("%q does not have access to org templates (code=%d)", org, apiError.Code)
+				return
+			}
+		} else if err != nil {
+			s.addError(err)
+			logging.Warningf("Failed to get templates from %q: %s", org, err.Error())
+			return
+		} else if orgTemplates.HasAccessError {
+			logging.Warningf("Failed to get templates from %q: Access Denied", org)
+			return
+		} else if orgTemplates.HasUpstreamError {
+			logging.Warningf("Failed to get templates from %q: Upstream Error", org)
+			return
+		}
+
+		for source, sourceTemplates := range orgTemplates.Templates {
+			logging.Infof("sourcing templates from %q", source)
+			for _, template := range sourceTemplates {
+				// These template are maintained using https://github.com/pulumi/templates, and are
+				// ingested without going through the Pulumi Cloud.
+				//
+				//
+				if strings.HasPrefix(template.TemplateURL, "https://github.com/pulumi/templates") {
+					continue
+				}
+
+				// Check if we already have this template from another source.
+				if _, ok := alreadySeenSourceURLs[template.TemplateURL]; ok {
+					// Skip a template that we have already seen.
+					continue
+				}
+				alreadySeenSourceURLs[template.TemplateURL] = struct{}{}
+
+				// If we are searching for a template of a specific name,
+				// only match templates of that name.
+				if templateName != "" && templateName != template.Name {
+					logging.V(10).Infof("skipping template %q", template.Name)
+					continue
+				}
+
+				logging.V(10).Infof("adding template %q", template.Name)
+				s.addTemplate(Template{
+					name:               template.Name,
+					description:        "", // No description present.
+					projectDescription: template.Description,
+					source:             s,
+					download: func(ctx context.Context) (workspace.Template, error) {
+						templateDir, err := os.MkdirTemp("", "pulumi-template-")
+						if err != nil {
+							return workspace.Template{}, err
+						}
+						// Having created a template directory, we now add it to the list of directories to close.
+						s.addCloser(func() error { return os.RemoveAll(templateDir) })
+
+						tarReader, err := b.DownloadTemplate(ctx, org, template.TemplateURL)
+						if err != nil {
+							return workspace.Template{}, err
+						}
+						if err := errors.Join(
+							writeTar(ctx, tarReader.Tar(), templateDir),
+							tarReader.Close(),
+						); err != nil {
+							return workspace.Template{}, err
+						}
+						logging.Infof("downloaded %q into %q", template.Name, templateDir)
+
+						return workspace.LoadTemplate(templateDir)
+					},
+				})
+			}
+		}
+	}
+
+	for _, org := range orgs {
+		wg.Add(1)
+		go handleOrg(org)
+	}
+}
+
+func writeTar(_ context.Context, reader *tar.Reader, dst string) error {
+	for {
+		header, err := reader.Next()
+		if errors.Is(err, io.EOF) {
+			return nil
+		} else if err != nil {
+			return err
+		}
+
+		logging.V(8).Infof("Decompressing %q", header.Name)
+
+		path := filepath.Clean(header.Name)
+		if !filepath.IsLocal(path) {
+			return fmt.Errorf("refusing to write non-local path %q", path)
+		}
+
+		target := filepath.Join(dst, path)
+
+		// Ensure that we can write the directory
+		if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
+			return err
+		}
+
+		switch header.Typeflag {
+		case tar.TypeDir:
+			if header.Mode > math.MaxUint32 {
+				return fmt.Errorf("invalid file mode for %q: %02x", header.Name, header.Mode)
+			}
+
+			fileMode := os.FileMode(header.Mode) //nolint:gosec // We checked the overflow
+			err := os.Mkdir(target, fileMode)
+			if err != nil && !errors.Is(err, fs.ErrExist) {
+				return err
+			}
+
+		case tar.TypeReg:
+			if header.Mode > math.MaxUint32 {
+				return fmt.Errorf("invalid file mode for %q: %02x", header.Name, header.Mode)
+			}
+
+			fileMode := os.FileMode(header.Mode) //nolint:gosec // We checked the overflow
+			f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, fileMode)
+			if err != nil {
+				return err
+			}
+
+			// Write the tar file into f
+			if _, err := io.Copy(f, reader); err != nil {
+				return errors.Join(err, f.Close())
+			}
+
+			// Close f. We don't use defer because we want to close each file
+			// after we open it, not leave it all until the download function
+			// exits.
+			if err := f.Close(); err != nil {
path: pkg/cmd/pulumi/templates/org.go
user: login: iwahbe
body: We do close if `io.Copy` fails:

https://github.com/pulumi/pulumi/blob/13700cd0f955f1e51a10edae44ef107de0c13661/pkg/cmd/pulumi/templates/org.go#L209-L211

I changed the implementation to include an immediately invoked closure so it's more obvious that `f.Close` is always called.
created_at: 2025-03-21T10:06:32Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,221 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package templates
+
+import (
+	"archive/tar"
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"io/fs"
+	"math"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/pkg/v3/backend"
+	"github.com/pulumi/pulumi/pkg/v3/backend/display"
+	cmdBackend "github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/backend"
+	pkgWorkspace "github.com/pulumi/pulumi/pkg/v3/workspace"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+func (s *Source) getOrgTemplates(
+	ctx context.Context, templateName string,
+	interactive bool, wg *sync.WaitGroup,
+) {
+	ws := pkgWorkspace.Instance
+	project, _, err := ws.ReadProject()
+	if err != nil && !errors.Is(err, workspace.ErrProjectNotFound) {
+		s.addError(err)
+		return
+	}
+
+	b, err := cmdBackend.CurrentBackend(ctx, ws, cmdBackend.DefaultLoginManager, project, display.Options{
+		Color:         cmdutil.GetGlobalColorization(),
+		IsInteractive: interactive,
+	})
+	if err != nil {
+		if !errors.Is(err, backend.MissingEnvVarForNonInteractiveError{}) {
+			s.addError(err)
+		}
+		return
+	}
+	if !b.SupportsTemplates() {
+		return
+	}
+
+	logging.Infof("Listing Org Templates from the cloud")
+	user, orgs, _, err := b.CurrentUser()
+	if err != nil {
+		s.addError(err)
+		return
+	} else if user == "" {
+		return // No current user - so don't proceed.
+	}
+
+	alreadySeenSourceURLs := map[string]struct{}{}
+
+	handleOrg := func(org string) {
+		defer wg.Done()
+		logging.Infof("Checking for templates from %q", org)
+		orgTemplates, err := b.ListTemplates(ctx, org)
+		if apiError := new(apitype.ErrorResponse); errors.As(err, &apiError) {
+			// This is what happens when we try to access org templates for an org that hasn't enabled org templates.
+			if apiError.Code == 402 {
+				logging.Infof("%q does not have access to org templates (code=%d)", org, apiError.Code)
+				return
+			}
+		} else if err != nil {
+			s.addError(err)
+			logging.Warningf("Failed to get templates from %q: %s", org, err.Error())
+			return
+		} else if orgTemplates.HasAccessError {
+			logging.Warningf("Failed to get templates from %q: Access Denied", org)
+			return
+		} else if orgTemplates.HasUpstreamError {
+			logging.Warningf("Failed to get templates from %q: Upstream Error", org)
+			return
+		}
+
+		for source, sourceTemplates := range orgTemplates.Templates {
+			logging.Infof("sourcing templates from %q", source)
+			for _, template := range sourceTemplates {
+				// These template are maintained using https://github.com/pulumi/templates, and are
+				// ingested without going through the Pulumi Cloud.
+				//
+				//
+				if strings.HasPrefix(template.TemplateURL, "https://github.com/pulumi/templates") {
+					continue
+				}
+
+				// Check if we already have this template from another source.
+				if _, ok := alreadySeenSourceURLs[template.TemplateURL]; ok {
+					// Skip a template that we have already seen.
+					continue
+				}
+				alreadySeenSourceURLs[template.TemplateURL] = struct{}{}
+
+				// If we are searching for a template of a specific name,
+				// only match templates of that name.
+				if templateName != "" && templateName != template.Name {
+					logging.V(10).Infof("skipping template %q", template.Name)
+					continue
+				}
+
+				logging.V(10).Infof("adding template %q", template.Name)
+				s.addTemplate(Template{
+					name:               template.Name,
+					description:        "", // No description present.
path: pkg/cmd/pulumi/templates/org.go
user: login: iwahbe
body: Org templates probably wont support descriptions in the near future, since the JSON they pass now already has a description field that maps to `Template.Description`.

There is very little use for non-template descriptions in templates as far as I can tell.
created_at: 2025-03-21T10:15:03Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,227 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package templates adds an abstraction for project templates that may be local or
+// remote.
+//
+// All templates are convertible into [workspace.Template].
+package templates
+
+import (
+	"context"
+	"errors"
+	"io/fs"
+	"os"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+// Source provides access to a set of project templates, any set of which may be present on
+// disk.
+//
+// Source is responsible for cleaning up old templates, and should always be [Close]d when
+// created.
+type Source struct {
+	templates    []Template
+	errorOnEmpty []error
+	errors       []error
+	closers      []func() error
+	closed       bool
+
+	// m should be held whenever Source is mutated.
+	m  sync.Mutex
+	wg sync.WaitGroup
+}
+
+// Templates lists the templates available to the [Source].
+func (s *Source) Templates() ([]Template, error) {
+	s.wg.Wait() // Wait to ensure that all templates have been fetched before returning the template list.
+
+	s.lockOpen("read templates")
+	defer s.m.Unlock()
+	if err := errors.Join(s.errors...); err != nil {
+		return nil, err
+	}
+	if len(s.templates) == 0 {
+		return nil, errors.Join(s.errorOnEmpty...)
+	}
+	return s.templates, nil
+}
+
+func (s *Source) addTemplate(t Template) {
+	s.lockOpen("add template")
+	s.templates = append(s.templates, t)
+	s.m.Unlock()
+}
+
+func (s *Source) addCloser(f func() error) {
+	s.lockOpen("add closer")
+	s.closers = append(s.closers, f)
+	s.m.Unlock()
+}
+
+func (s *Source) addError(err error) {
+	s.lockOpen("add error")
+	s.errors = append(s.errors, err)
+	s.m.Unlock()
+}
+
+func (s *Source) addErrorOnEmpty(err error) {
+	s.lockOpen("add error on empty")
+	s.errorOnEmpty = append(s.errorOnEmpty, err)
+	s.m.Unlock()
+}
+
+func (s *Source) lockOpen(action string) {
+	s.m.Lock()
+	if s.closed {
+		panic("Attempted to act on closed source: " + action)
+	}
+}
+
+// Close cleans up the [Source] and any associated templates.
+//
+// Close should always be called when [Source] is dropped.
+func (s *Source) Close() error {
+	s.wg.Wait() // Wait to ensure that all templates have been fetched so all closers are visible.
path: pkg/cmd/pulumi/templates/templates.go
user: login: iwahbe
body: Yes. Good call.
created_at: 2025-03-21T10:20:21Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,221 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package templates
+
+import (
+	"archive/tar"
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"io/fs"
+	"math"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/pkg/v3/backend"
+	"github.com/pulumi/pulumi/pkg/v3/backend/display"
+	cmdBackend "github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/backend"
+	pkgWorkspace "github.com/pulumi/pulumi/pkg/v3/workspace"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+func (s *Source) getOrgTemplates(
+	ctx context.Context, templateName string,
+	interactive bool, wg *sync.WaitGroup,
+) {
+	ws := pkgWorkspace.Instance
+	project, _, err := ws.ReadProject()
+	if err != nil && !errors.Is(err, workspace.ErrProjectNotFound) {
+		s.addError(err)
+		return
+	}
+
+	b, err := cmdBackend.CurrentBackend(ctx, ws, cmdBackend.DefaultLoginManager, project, display.Options{
+		Color:         cmdutil.GetGlobalColorization(),
+		IsInteractive: interactive,
+	})
+	if err != nil {
+		if !errors.Is(err, backend.MissingEnvVarForNonInteractiveError{}) {
+			s.addError(err)
+		}
+		return
+	}
+	if !b.SupportsTemplates() {
+		return
+	}
+
+	logging.Infof("Listing Org Templates from the cloud")
+	user, orgs, _, err := b.CurrentUser()
+	if err != nil {
+		s.addError(err)
+		return
+	} else if user == "" {
+		return // No current user - so don't proceed.
+	}
+
+	alreadySeenSourceURLs := map[string]struct{}{}
+
+	handleOrg := func(org string) {
+		defer wg.Done()
+		logging.Infof("Checking for templates from %q", org)
+		orgTemplates, err := b.ListTemplates(ctx, org)
+		if apiError := new(apitype.ErrorResponse); errors.As(err, &apiError) {
+			// This is what happens when we try to access org templates for an org that hasn't enabled org templates.
+			if apiError.Code == 402 {
+				logging.Infof("%q does not have access to org templates (code=%d)", org, apiError.Code)
+				return
+			}
+		} else if err != nil {
+			s.addError(err)
+			logging.Warningf("Failed to get templates from %q: %s", org, err.Error())
+			return
+		} else if orgTemplates.HasAccessError {
+			logging.Warningf("Failed to get templates from %q: Access Denied", org)
+			return
+		} else if orgTemplates.HasUpstreamError {
+			logging.Warningf("Failed to get templates from %q: Upstream Error", org)
+			return
path: pkg/cmd/pulumi/templates/org.go
user: login: iwahbe
body: I can improve the error message for `.HasAccessError`, but I can't do much for `.HasUpstreamError`. `HasUpstreamError` indicates "that there was an unspecified error fetching a template". I'll add a comment to that effect.
created_at: 2025-03-21T10:34:55Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,221 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package templates
+
+import (
+	"archive/tar"
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"io/fs"
+	"math"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/pkg/v3/backend"
+	"github.com/pulumi/pulumi/pkg/v3/backend/display"
+	cmdBackend "github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/backend"
+	pkgWorkspace "github.com/pulumi/pulumi/pkg/v3/workspace"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+func (s *Source) getOrgTemplates(
+	ctx context.Context, templateName string,
+	interactive bool, wg *sync.WaitGroup,
+) {
+	ws := pkgWorkspace.Instance
+	project, _, err := ws.ReadProject()
+	if err != nil && !errors.Is(err, workspace.ErrProjectNotFound) {
+		s.addError(err)
+		return
+	}
+
+	b, err := cmdBackend.CurrentBackend(ctx, ws, cmdBackend.DefaultLoginManager, project, display.Options{
+		Color:         cmdutil.GetGlobalColorization(),
+		IsInteractive: interactive,
+	})
+	if err != nil {
+		if !errors.Is(err, backend.MissingEnvVarForNonInteractiveError{}) {
+			s.addError(err)
+		}
+		return
+	}
+	if !b.SupportsTemplates() {
+		return
+	}
+
+	logging.Infof("Listing Org Templates from the cloud")
+	user, orgs, _, err := b.CurrentUser()
+	if err != nil {
+		s.addError(err)
+		return
+	} else if user == "" {
+		return // No current user - so don't proceed.
+	}
+
+	alreadySeenSourceURLs := map[string]struct{}{}
+
+	handleOrg := func(org string) {
+		defer wg.Done()
+		logging.Infof("Checking for templates from %q", org)
+		orgTemplates, err := b.ListTemplates(ctx, org)
+		if apiError := new(apitype.ErrorResponse); errors.As(err, &apiError) {
+			// This is what happens when we try to access org templates for an org that hasn't enabled org templates.
+			if apiError.Code == 402 {
+				logging.Infof("%q does not have access to org templates (code=%d)", org, apiError.Code)
path: pkg/cmd/pulumi/templates/org.go
user: login: iwahbe
body: We do silently not use them. Unless the user increases log verbosity, we don't show logs until they are at an `Error` severity.
created_at: 2025-03-21T10:40:34Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,221 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package templates
+
+import (
+	"archive/tar"
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"io/fs"
+	"math"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/pkg/v3/backend"
+	"github.com/pulumi/pulumi/pkg/v3/backend/display"
+	cmdBackend "github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/backend"
+	pkgWorkspace "github.com/pulumi/pulumi/pkg/v3/workspace"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+func (s *Source) getOrgTemplates(
+	ctx context.Context, templateName string,
+	interactive bool, wg *sync.WaitGroup,
+) {
+	ws := pkgWorkspace.Instance
+	project, _, err := ws.ReadProject()
+	if err != nil && !errors.Is(err, workspace.ErrProjectNotFound) {
+		s.addError(err)
+		return
+	}
+
+	b, err := cmdBackend.CurrentBackend(ctx, ws, cmdBackend.DefaultLoginManager, project, display.Options{
+		Color:         cmdutil.GetGlobalColorization(),
+		IsInteractive: interactive,
+	})
+	if err != nil {
+		if !errors.Is(err, backend.MissingEnvVarForNonInteractiveError{}) {
+			s.addError(err)
+		}
+		return
+	}
+	if !b.SupportsTemplates() {
+		return
+	}
+
+	logging.Infof("Listing Org Templates from the cloud")
path: pkg/cmd/pulumi/templates/org.go
user: login: iwahbe
body: `logging` doesn't have a debug level. That said, unless the user explicitly increases the log level, this will not be shows to users.

I have added `logging.Infof` messages for other exit paths.
created_at: 2025-03-21T10:45:54Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,227 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package templates adds an abstraction for project templates that may be local or
+// remote.
+//
+// All templates are convertible into [workspace.Template].
+package templates
+
+import (
+	"context"
+	"errors"
+	"io/fs"
+	"os"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+// Source provides access to a set of project templates, any set of which may be present on
+// disk.
+//
+// Source is responsible for cleaning up old templates, and should always be [Close]d when
+// created.
+type Source struct {
+	templates    []Template
+	errorOnEmpty []error
+	errors       []error
+	closers      []func() error
+	closed       bool
+
+	// m should be held whenever Source is mutated.
+	m  sync.Mutex
+	wg sync.WaitGroup
+}
+
+// Templates lists the templates available to the [Source].
+func (s *Source) Templates() ([]Template, error) {
+	s.wg.Wait() // Wait to ensure that all templates have been fetched before returning the template list.
+
+	s.lockOpen("read templates")
+	defer s.m.Unlock()
+	if err := errors.Join(s.errors...); err != nil {
+		return nil, err
+	}
+	if len(s.templates) == 0 {
+		return nil, errors.Join(s.errorOnEmpty...)
+	}
+	return s.templates, nil
+}
+
+func (s *Source) addTemplate(t Template) {
+	s.lockOpen("add template")
+	s.templates = append(s.templates, t)
+	s.m.Unlock()
+}
+
+func (s *Source) addCloser(f func() error) {
+	s.lockOpen("add closer")
+	s.closers = append(s.closers, f)
+	s.m.Unlock()
+}
+
+func (s *Source) addError(err error) {
+	s.lockOpen("add error")
+	s.errors = append(s.errors, err)
+	s.m.Unlock()
+}
+
+func (s *Source) addErrorOnEmpty(err error) {
+	s.lockOpen("add error on empty")
+	s.errorOnEmpty = append(s.errorOnEmpty, err)
+	s.m.Unlock()
+}
+
+func (s *Source) lockOpen(action string) {
+	s.m.Lock()
+	if s.closed {
+		panic("Attempted to act on closed source: " + action)
+	}
+}
+
+// Close cleans up the [Source] and any associated templates.
+//
+// Close should always be called when [Source] is dropped.
+func (s *Source) Close() error {
+	s.wg.Wait() // Wait to ensure that all templates have been fetched so all closers are visible.
+
+	s.lockOpen("close")
+	defer s.m.Unlock()
+	s.closed = true
+	errs := make([]error, len(s.closers))
+	for i, f := range s.closers {
+		errs[i] = f()
+	}
+	return errors.Join(errs...)
+}
+
+type Template struct {
+	name               string
+	description        string
+	projectDescription string
+
+	error error
+
+	download func(ctx context.Context) (workspace.Template, error)
+
+	source *Source
+}
+
+func (t Template) Name() string               { return t.name }
+func (t Template) Description() string        { return t.description }
+func (t Template) ProjectDescription() string { return t.projectDescription }
+func (t Template) Error() error               { return t.error }
+
+func (t Template) Download(ctx context.Context) (workspace.Template, error) {
+	if t.source == nil {
+		panic("Cannot download a template without a host")
+	}
+	if t.source.closed {
+		panic("Cannot download a template from an already closed host")
+	}
+
+	return t.download(ctx)
+}
+
+type Scope struct{ kind string }
+
+var (
+	ScopeDefault     = Scope{}
+	scopeDefault     = ScopeAll
+	ScopeTraditional = Scope{"traditional"}
+	ScopeLocal       = Scope{"none"}
+	ScopeAll         = Scope{"all"}
+)
+
+// Create a new [Template] [Source] associated with a given [Scope].
+func New(
+	ctx context.Context, templateNamePathOrURL string, scope Scope,
+	templateKind workspace.TemplateKind, interactive bool,
+) *Source {
+	// apply the default scope, if necessary
+	if scope == ScopeDefault {
+		scope = scopeDefault
+	}
+
+	var source Source
+	ctx, cancel := context.WithCancel(ctx)
+	source.closers = append(source.closers, func() error { cancel(); return nil })
+
+	queryKind := getTemplateQuery(templateNamePathOrURL)
+
+	if scope == ScopeAll || scope == ScopeTraditional || scope == ScopeLocal {
+		source.wg.Add(1)
+		go func() {
+			source.getWorkspaceTemplates(ctx, templateNamePathOrURL, scope, templateKind, &source.wg)
+			source.wg.Done()
+		}()
+	}
+
+	if scope == ScopeAll && templateKind == workspace.TemplateKindPulumiProject && queryKind == queryName {
path: pkg/cmd/pulumi/templates/templates.go
user: login: iwahbe
body: Sure
created_at: 2025-03-21T10:47:16Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,227 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package templates adds an abstraction for project templates that may be local or
+// remote.
+//
+// All templates are convertible into [workspace.Template].
+package templates
+
+import (
+	"context"
+	"errors"
+	"io/fs"
+	"os"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+// Source provides access to a set of project templates, any set of which may be present on
+// disk.
+//
+// Source is responsible for cleaning up old templates, and should always be [Close]d when
+// created.
+type Source struct {
+	templates    []Template
+	errorOnEmpty []error
+	errors       []error
+	closers      []func() error
+	closed       bool
+
+	// m should be held whenever Source is mutated.
+	m  sync.Mutex
+	wg sync.WaitGroup
+}
+
+// Templates lists the templates available to the [Source].
+func (s *Source) Templates() ([]Template, error) {
+	s.wg.Wait() // Wait to ensure that all templates have been fetched before returning the template list.
+
+	s.lockOpen("read templates")
+	defer s.m.Unlock()
+	if err := errors.Join(s.errors...); err != nil {
+		return nil, err
+	}
+	if len(s.templates) == 0 {
+		return nil, errors.Join(s.errorOnEmpty...)
+	}
+	return s.templates, nil
+}
+
+func (s *Source) addTemplate(t Template) {
+	s.lockOpen("add template")
+	s.templates = append(s.templates, t)
+	s.m.Unlock()
+}
+
+func (s *Source) addCloser(f func() error) {
+	s.lockOpen("add closer")
+	s.closers = append(s.closers, f)
+	s.m.Unlock()
+}
+
+func (s *Source) addError(err error) {
+	s.lockOpen("add error")
+	s.errors = append(s.errors, err)
+	s.m.Unlock()
+}
+
+func (s *Source) addErrorOnEmpty(err error) {
+	s.lockOpen("add error on empty")
+	s.errorOnEmpty = append(s.errorOnEmpty, err)
+	s.m.Unlock()
+}
+
+func (s *Source) lockOpen(action string) {
+	s.m.Lock()
+	if s.closed {
+		panic("Attempted to act on closed source: " + action)
+	}
+}
+
+// Close cleans up the [Source] and any associated templates.
+//
+// Close should always be called when [Source] is dropped.
+func (s *Source) Close() error {
+	s.wg.Wait() // Wait to ensure that all templates have been fetched so all closers are visible.
+
+	s.lockOpen("close")
+	defer s.m.Unlock()
+	s.closed = true
+	errs := make([]error, len(s.closers))
+	for i, f := range s.closers {
+		errs[i] = f()
+	}
+	return errors.Join(errs...)
+}
+
+type Template struct {
+	name               string
+	description        string
+	projectDescription string
+
+	error error
+
+	download func(ctx context.Context) (workspace.Template, error)
+
+	source *Source
+}
+
+func (t Template) Name() string               { return t.name }
+func (t Template) Description() string        { return t.description }
+func (t Template) ProjectDescription() string { return t.projectDescription }
+func (t Template) Error() error               { return t.error }
+
+func (t Template) Download(ctx context.Context) (workspace.Template, error) {
+	if t.source == nil {
+		panic("Cannot download a template without a host")
+	}
+	if t.source.closed {
+		panic("Cannot download a template from an already closed host")
+	}
+
+	return t.download(ctx)
+}
+
+type Scope struct{ kind string }
+
+var (
+	ScopeDefault     = Scope{}
+	scopeDefault     = ScopeAll
+	ScopeTraditional = Scope{"traditional"}
+	ScopeLocal       = Scope{"none"}
+	ScopeAll         = Scope{"all"}
+)
+
+// Create a new [Template] [Source] associated with a given [Scope].
+func New(
+	ctx context.Context, templateNamePathOrURL string, scope Scope,
+	templateKind workspace.TemplateKind, interactive bool,
+) *Source {
+	// apply the default scope, if necessary
+	if scope == ScopeDefault {
+		scope = scopeDefault
+	}
+
+	var source Source
+	ctx, cancel := context.WithCancel(ctx)
+	source.closers = append(source.closers, func() error { cancel(); return nil })
+
+	queryKind := getTemplateQuery(templateNamePathOrURL)
+
+	if scope == ScopeAll || scope == ScopeTraditional || scope == ScopeLocal {
+		source.wg.Add(1)
+		go func() {
+			source.getWorkspaceTemplates(ctx, templateNamePathOrURL, scope, templateKind, &source.wg)
+			source.wg.Done()
+		}()
+	}
+
+	if scope == ScopeAll && templateKind == workspace.TemplateKindPulumiProject && queryKind == queryName {
+		source.wg.Add(1)
+		go func() {
+			source.getOrgTemplates(ctx, templateNamePathOrURL, interactive, &source.wg)
+			source.wg.Done()
+		}()
+	}
+
+	return &source
+}
+
+type queryKind string
+
+const (
+	queryName queryKind = "query-name"
+	queryURL  queryKind = "query-url"
+	queryPath queryKind = "query-path"
+)
path: pkg/cmd/pulumi/templates/templates.go
user: login: iwahbe
body: Made irrelevent by https://github.com/pulumi/pulumi/pull/18627#discussion_r2007136988.
created_at: 2025-03-21T10:49:04Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,118 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package templates
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"net/http"
+	"net/url"
+	"sync"
+
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+func (s *Source) getWorkspaceTemplates(
+	ctx context.Context, templateNamePathOrURL string, scope Scope, templateKind workspace.TemplateKind,
+	_ *sync.WaitGroup,
+) {
+	repo, err := workspace.RetrieveTemplates(ctx, templateNamePathOrURL, scope == ScopeLocal, templateKind)
+	if err != nil {
+		if notFound := new(workspace.TemplateNotFoundError); errors.As(err, notFound) {
+			s.addErrorOnEmpty(notFound)
+			return
+		}
+		// Bail on all errors unless its a 401 from a Pulumi Cloud backend...
+		if !errors.Is(err, workspace.ErrPulumiCloudUnauthorized) {
+			s.addError(err)
+			return
+		}
+
+		// ...If the request has 401'd AND we've identified the backend as being a Pulumi Cloud instance, we can
+		// attempt to retrieve the template using the user's Pulumi Cloud credentials.
+		repo, err = retrievePrivatePulumiCloudTemplate(templateNamePathOrURL)
+		if err != nil {
+			s.addError(err)
+			return
+		}
+	}
+	s.addCloser(repo.Delete)
+	workspaceTemplates, err := repo.Templates()
+	if err != nil {
+		s.addError(err)
+		return
+	}
+
+	s.addDownloadedTemplates(workspaceTemplates)
+}
+
+// Retrieve a Private template from the given Pulumi Cloud URL **including an auth token for Pulumi Cloud**.
+//
+// workspace.GetAccount ensures the user has a valid session with the Pulumi Cloud backend.
+//   - If the user is not logged in, the login flow will be initiated.
path: pkg/cmd/pulumi/templates/workspace.go
user: login: iwahbe
body: Ah, my bad there.

This comment was left over from me trying to understand this function (which was factored out from elsewhere). We don't need to refactor.
created_at: 2025-03-21T10:52:47Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,227 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package templates adds an abstraction for project templates that may be local or
+// remote.
+//
+// All templates are convertible into [workspace.Template].
+package templates
path: pkg/cmd/pulumi/templates/templates.go
user: login: iwahbe
body: I'll add some. Thanks for keeping me honest here.

We can mock out remote downloads by setting `pkgWorkspace.Instance` and `backend.BackendInstance`. I'll see about mocking out `workspace.RetrieveTemplates`.
created_at: 2025-03-21T11:01:14Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,221 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package templates
+
+import (
+	"archive/tar"
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"io/fs"
+	"math"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/pkg/v3/backend"
+	"github.com/pulumi/pulumi/pkg/v3/backend/display"
+	cmdBackend "github.com/pulumi/pulumi/pkg/v3/cmd/pulumi/backend"
+	pkgWorkspace "github.com/pulumi/pulumi/pkg/v3/workspace"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+func (s *Source) getOrgTemplates(
+	ctx context.Context, templateName string,
+	interactive bool, wg *sync.WaitGroup,
+) {
+	ws := pkgWorkspace.Instance
+	project, _, err := ws.ReadProject()
+	if err != nil && !errors.Is(err, workspace.ErrProjectNotFound) {
+		s.addError(err)
+		return
+	}
+
+	b, err := cmdBackend.CurrentBackend(ctx, ws, cmdBackend.DefaultLoginManager, project, display.Options{
+		Color:         cmdutil.GetGlobalColorization(),
+		IsInteractive: interactive,
+	})
+	if err != nil {
+		if !errors.Is(err, backend.MissingEnvVarForNonInteractiveError{}) {
+			s.addError(err)
+		}
+		return
+	}
+	if !b.SupportsTemplates() {
+		return
+	}
+
+	logging.Infof("Listing Org Templates from the cloud")
path: pkg/cmd/pulumi/templates/org.go
user: login: tgummerer
body: That's ... confusing.  But it's what's already happening, so let's not change it in this PR.
created_at: 2025-03-24T10:30:31Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,211 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package templates adds an abstraction for project templates that may be local or
+// remote.
+//
+// All templates are convertible into [workspace.Template].
+package templates
+
+import (
+	"context"
+	"errors"
+	"io/fs"
+	"os"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+// Source provides access to a set of project templates, any set of which may be present on
+// disk.
+//
+// Source is responsible for cleaning up old templates, and should always be [Close]d when
+// created.
+type Source struct {
+	templates    []Template
+	errorOnEmpty []error
+	errors       []error
+	// closers holds a list of functions to be invoked when the Source is closed.
+	//
+	// closers always has length at least one, and the first closer always cancels the
+	// context associated with fetch operations.
+	closers []func() error
+	closed  bool
+
+	// m should be held whenever Source is mutated.
+	m  sync.Mutex
+	wg sync.WaitGroup
+}
+
+// Templates lists the templates available to the [Source].
+//
+// Templates *does not* produce a sorted list. If templates need to be sorted, then the
+// caller is responsible for sorting them.
+func (s *Source) Templates() ([]Template, error) {
+	s.wg.Wait() // Wait to ensure that all templates have been fetched before returning the template list.
+
+	s.lockOpen("read templates")
+	defer s.m.Unlock()
+	if err := errors.Join(s.errors...); err != nil {
+		return nil, err
+	}
+	if len(s.templates) == 0 {
+		return nil, errors.Join(s.errorOnEmpty...)
+	}
+	return s.templates, nil
+}
+
+func (s *Source) addTemplate(t Template) {
+	contract.Assertf(t != nil, "We should never return nil templates")
+	s.lockOpen("add template")
+	s.templates = append(s.templates, t)
+	s.m.Unlock()
+}
+
+func (s *Source) addCloser(f func() error) {
+	s.lockOpen("add closer")
+	s.closers = append(s.closers, f)
path: pkg/cmd/pulumi/templates/templates.go
user: login: tgummerer
body: CI seems to detect a race here.  Not entirely sure how that's possible since we do seem to have a lock used, but we should look at that.
created_at: 2025-03-24T10:33:22Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,211 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package templates adds an abstraction for project templates that may be local or
+// remote.
+//
+// All templates are convertible into [workspace.Template].
+package templates
+
+import (
+	"context"
+	"errors"
+	"io/fs"
+	"os"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+// Source provides access to a set of project templates, any set of which may be present on
+// disk.
+//
+// Source is responsible for cleaning up old templates, and should always be [Close]d when
+// created.
+type Source struct {
+	templates    []Template
+	errorOnEmpty []error
+	errors       []error
+	// closers holds a list of functions to be invoked when the Source is closed.
+	//
+	// closers always has length at least one, and the first closer always cancels the
+	// context associated with fetch operations.
+	closers []func() error
+	closed  bool
+
+	// m should be held whenever Source is mutated.
+	m  sync.Mutex
+	wg sync.WaitGroup
+}
+
+// Templates lists the templates available to the [Source].
+//
+// Templates *does not* produce a sorted list. If templates need to be sorted, then the
+// caller is responsible for sorting them.
+func (s *Source) Templates() ([]Template, error) {
+	s.wg.Wait() // Wait to ensure that all templates have been fetched before returning the template list.
+
+	s.lockOpen("read templates")
+	defer s.m.Unlock()
+	if err := errors.Join(s.errors...); err != nil {
+		return nil, err
+	}
+	if len(s.templates) == 0 {
+		return nil, errors.Join(s.errorOnEmpty...)
+	}
+	return s.templates, nil
+}
+
+func (s *Source) addTemplate(t Template) {
+	contract.Assertf(t != nil, "We should never return nil templates")
+	s.lockOpen("add template")
+	s.templates = append(s.templates, t)
+	s.m.Unlock()
+}
+
+func (s *Source) addCloser(f func() error) {
+	s.lockOpen("add closer")
+	s.closers = append(s.closers, f)
+	s.m.Unlock()
+}
+
+func (s *Source) addError(err error) {
+	s.lockOpen("add error")
+	s.errors = append(s.errors, err)
+	s.m.Unlock()
+}
+
+func (s *Source) addErrorOnEmpty(err error) {
+	s.lockOpen("add error on empty")
+	s.errorOnEmpty = append(s.errorOnEmpty, err)
+	s.m.Unlock()
+}
+
+func (s *Source) lockOpen(action string) {
+	s.m.Lock()
+	contract.Assertf(!s.closed, "Attempted to act on closed source: "+action)
+}
+
+// Close cleans up the [Source] and any associated templates.
+//
+// Close should always be called when [Source] is dropped.
+func (s *Source) Close() error {
+	contract.IgnoreError(s.closers[0]())
path: pkg/cmd/pulumi/templates/templates.go
user: login: tgummerer
body: Probably deserves a comment that we're calling the "cancel" closer here.  But also maybe we should have that separate rather than in the list of other "closers", since there can only ever be one, and it's kinda unrelated to the others.
created_at: 2025-03-24T10:37:57Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,211 @@
+// Copyright 2016-2025, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package templates adds an abstraction for project templates that may be local or
+// remote.
+//
+// All templates are convertible into [workspace.Template].
+package templates
+
+import (
+	"context"
+	"errors"
+	"io/fs"
+	"os"
+	"strings"
+	"sync"
+
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
+	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
+)
+
+// Source provides access to a set of project templates, any set of which may be present on
+// disk.
+//
+// Source is responsible for cleaning up old templates, and should always be [Close]d when
+// created.
+type Source struct {
+	templates    []Template
+	errorOnEmpty []error
+	errors       []error
+	// closers holds a list of functions to be invoked when the Source is closed.
+	//
+	// closers always has length at least one, and the first closer always cancels the
+	// context associated with fetch operations.
+	closers []func() error
+	closed  bool
+
+	// m should be held whenever Source is mutated.
+	m  sync.Mutex
+	wg sync.WaitGroup
+}
+
+// Templates lists the templates available to the [Source].
+//
+// Templates *does not* produce a sorted list. If templates need to be sorted, then the
+// caller is responsible for sorting them.
+func (s *Source) Templates() ([]Template, error) {
+	s.wg.Wait() // Wait to ensure that all templates have been fetched before returning the template list.
+
+	s.lockOpen("read templates")
+	defer s.m.Unlock()
+	if err := errors.Join(s.errors...); err != nil {
+		return nil, err
+	}
+	if len(s.templates) == 0 {
+		return nil, errors.Join(s.errorOnEmpty...)
+	}
+	return s.templates, nil
+}
+
+func (s *Source) addTemplate(t Template) {
+	contract.Assertf(t != nil, "We should never return nil templates")
+	s.lockOpen("add template")
+	s.templates = append(s.templates, t)
+	s.m.Unlock()
+}
+
+func (s *Source) addCloser(f func() error) {
+	s.lockOpen("add closer")
+	s.closers = append(s.closers, f)
path: pkg/cmd/pulumi/templates/templates.go
user: login: iwahbe
body: I bet it's actually related to https://github.com/pulumi/pulumi/pull/18627#discussion_r2009911704. `(*Source).addCloser` can be called at the same time as `(*Source).Close`, and `(*Source).Close` used to call `s.closers[0]()` **before** it takes the lock. This was necessary since canceled functions are still allowed to add closers, but also potentially racy if `s.closers` was re-allocating while cancel was being called.
created_at: 2025-03-24T15:32:21Z
author_association: MEMBER

PR comments:
user: login: kramhuber
created_at: 2025-03-06T21:48:30Z
body: @iwahbe could you share a screen grab of what the output of `-l` looks like when there's both org and non-org templates?
author_association: CONTRIBUTOR
user: login: iwahbe
created_at: 2025-03-07T10:55:15Z
body: > @iwahbe could you share a screen grab of what the output of `-l` looks like when there's both org and non-org templates?

Definitely. It's pretty long at this point:

<details>
<summary>
output
</summary>

```
Available Templates:
  NPW Test Templates/aws-go               A minimal AWS Go Pulumi program
  NPW Test Templates/kubernetes-aws-go    A Go program to deploy a Kubernetes cluster on AWS
  NPW Test Templates/private-gcp-go       A private minimal Google Cloud Go Pulumi program
  aiven-go                                A minimal Aiven Go Pulumi program
  aiven-python                            A minimal Aiven Python Pulumi program
  aiven-typescript                        A minimal Aiven TypeScript Pulumi program
  alicloud-csharp                         A minimal AliCloud C# Pulumi program
  alicloud-fsharp                         A minimal AliCloud F# Pulumi program
  alicloud-go                             A minimal AliCloud Go Pulumi program
  alicloud-javascript                     A minimal AliCloud JavaScript Pulumi program
  alicloud-python                         A minimal AliCloud Python Pulumi program
  alicloud-typescript                     A minimal AliCloud TypeScript Pulumi program
  alicloud-visualbasic                    A minimal AliCloud VB.NET Pulumi program
  alicloud-yaml                           A minimal AliCloud Pulumi YAML program
  auth0-csharp                            A minimal Auth0 C# Pulumi program
  auth0-go                                A minimal Auth0 Go Pulumi program
  auth0-javascript                        A minimal Auth0 TypeScript Pulumi program
  auth0-python                            A minimal Auth0 Python Pulumi program
  auth0-typescript                        A minimal Auth0 TypeScript Pulumi program
  auth0-yaml                              A minimal Auth0 Pulumi YAML program
  aws-csharp                              A minimal AWS C# Pulumi program
  aws-fsharp                              A minimal AWS F# Pulumi program
  aws-go                                  A minimal AWS Go Pulumi program
  aws-java                                A minimal AWS Java Pulumi program
  aws-javascript                          A minimal AWS JavaScript Pulumi program
  aws-python                              A minimal AWS Python Pulumi program
  aws-scala                               A minimal AWS Scala Pulumi program
  aws-typescript                          A minimal AWS TypeScript Pulumi program
  aws-visualbasic                         A minimal AWS VB.NET Pulumi program
  aws-yaml                                A minimal AWS Pulumi YAML program
  azure-classic-csharp                    A minimal C# Pulumi program with the classic Azure provider
  azure-classic-fsharp                    A minimal F# Pulumi program with the classic Azure provider
  azure-classic-go                        A minimal Go Pulumi program with the classic Azure provider
  azure-classic-javascript                A minimal JavaScript Pulumi program with the classic Azure provider
  azure-classic-python                    A minimal Python Pulumi program with the classic Azure provider
  azure-classic-typescript                A minimal TypeScript Pulumi program with the classic Azure provider
  azure-classic-visualbasic               A minimal VB.NET Pulumi program with the classic Azure provider
  azure-classic-yaml                      A minimal Pulumi YAML program with the classic Azure provider
  azure-csharp                            A minimal Azure Native C# Pulumi program
  azure-fsharp                            A minimal Azure Native F# Pulumi program
  azure-go                                A minimal Azure Native Go Pulumi program
  azure-java                              A minimal Azure Native Java Pulumi program
  azure-javascript                        A minimal JavaScript Pulumi program with the native Azure provider
  azure-python                            A minimal Azure Native Python Pulumi program
  azure-typescript                        A minimal Azure Native TypeScript Pulumi program
  azure-yaml                              A minimal Azure Native Pulumi YAML program
  azuredevops-python                      A minimal Azure DevOps Python Pulumi program to create an AzureDevOps Project
  civo-go                                 A minimal Civo Go Pulumi program
  civo-javascript                         A minimal Civo TypeScript Pulumi program
  civo-python                             A minimal Civo Python Pulumi program
  civo-typescript                         A minimal Civo TypeScript Pulumi program
  civo-yaml                               A minimal Civo Pulumi YAML program
  container-aws-csharp                    A C# program to deploy a containerized service on AWS
  container-aws-go                        A Go program to deploy a containerized service on AWS
  container-aws-python                    A Python program to deploy a containerized service on AWS
  container-aws-typescript                A TypeScript program to deploy a containerized service on AWS
  container-aws-yaml                      A Pulumi YAML program to deploy a containerized service on AWS
  container-azure-csharp                  A C# program to deploy a containerized service on Azure
  container-azure-go                      A Go program to deploy a containerized service on Azure
  container-azure-python                  A Python program to deploy a containerized service on Azure
  container-azure-typescript              A TypeScript program to deploy a containerized service on Azure
  container-gcp-csharp                    A C# program to deploy a containerized service on Google Cloud
  container-gcp-go                        A Go program to deploy a containerized service on Google Cloud
  container-gcp-python                    A Python program to deploy a containerized service on Google Cloud
  container-gcp-typescript                A TypeScript program to deploy a containerized service on Google Cloud
  csharp                                  A minimal C# Pulumi program
  digitalocean-go                         A minimal DigitalOcean Go Pulumi program
  digitalocean-javascript                 A minimal DigitalOcean JavaScript Pulumi program
  digitalocean-python                     A minimal DigitalOcean Python Pulumi program
  digitalocean-typescript                 A minimal DigitalOcean TypeScript Pulumi program
  digitalocean-yaml                       A minimal DigitalOcean Pulumi YAML program
  equinix-metal-go                        A minimal Equinix Metal Go Pulumi program
  equinix-metal-javascript                A minimal Equinix Metal JavaScript Pulumi program
  equinix-metal-python                    A minimal Equinix Metal Python Pulumi program
  equinix-metal-typescript                A minimal Equinix Metal TypeScript Pulumi program
  equinix-metal-yaml                      A minimal Equinix Metal Pulumi YAML program
  fsharp                                  A minimal F# Pulumi program
  gcp-csharp                              A minimal Google Cloud C# Pulumi program
  gcp-fsharp                              A minimal GCP F# Pulumi program
  gcp-go                                  A minimal Google Cloud Go Pulumi program
  gcp-java                                A minimal Google Cloud Java Pulumi program
  gcp-javascript                          A minimal Google Cloud JavaScript Pulumi program
  gcp-python                              A minimal Google Cloud Python Pulumi program
  gcp-typescript                          A minimal Google Cloud TypeScript Pulumi program
  gcp-visualbasic                         A minimal GCP VB.NET Pulumi program
  gcp-yaml                                A minimal Google Cloud Pulumi YAML program
  github-csharp                           A minimal GitHub C# Pulumi program
  github-go                               A minimal GitHub Go Pulumi program
  github-javascript                       A minimal GitHub JavaScript Pulumi program.
  github-python                           A minimal GitHub Python Pulumi program
  github-typescript                       A minimal GitHub TypeScript Pulumi program.
  github-yaml                             A minimal GitHub Pulumi YAML program.
  go                                      A minimal Go Pulumi program
  google-native-csharp                    A minimal Google Cloud C# Pulumi program
  google-native-go                        A minimal Google Cloud Go Pulumi program
  google-native-python                    A minimal Google Cloud Python Pulumi program
  google-native-typescript                A minimal Google Cloud TypeScript Pulumi program
  google-native-yaml                      A minimal Google Cloud Pulumi YAML program
  hello-aws-javascript                    A simple AWS serverless JavaScript Pulumi program
  helm-kubernetes-csharp                  A C# program to deploy a Helm chart onto a Kubernetes cluster
  helm-kubernetes-go                      A Go program to deploy a Helm chart onto a Kubernetes cluster
  helm-kubernetes-python                  A Python program to deploy a Helm chart onto a Kubernetes cluster
  helm-kubernetes-typescript              A TypeScript program to deploy a Helm chart onto a Kubernetes cluster
  helm-kubernetes-yaml                    A Pulumi YAML program to deploy a Helm chart onto a Kubernetes cluster
  java                                    A minimal Java Pulumi program with Maven builds
  java-gradle                             A minimal Java Pulumi program with Gradle builds
  java-jbang                              A minimal Java Pulumi program with JBang
  javascript                              A minimal JavaScript Pulumi program
  kubernetes-aws-csharp                   A C# program to deploy a Kubernetes cluster on AWS
  kubernetes-aws-go                       A Go program to deploy a Kubernetes cluster on AWS
  kubernetes-aws-python                   A Python program to deploy a Kubernetes cluster on AWS
  kubernetes-aws-typescript               A TypeScript program to deploy a Kubernetes cluster on AWS
  kubernetes-aws-yaml                     A Pulumi YAML program to deploy a Kubernetes cluster on AWS
  kubernetes-azure-csharp                 A C# program to deploy a Kubernetes cluster on Azure
  kubernetes-azure-go                     A Go program to deploy a Kubernetes cluster on Azure
  kubernetes-azure-python                 A Python program to deploy a Kubernetes cluster on Azure
  kubernetes-azure-typescript             A TypeScript program to deploy a Kubernetes cluster on Azure
  kubernetes-azure-yaml                   A Pulumi YAML program to deploy a Kubernetes cluster on Azure
  kubernetes-csharp                       A minimal Kubernetes C# Pulumi program
  kubernetes-fsharp                       A minimal Kubernetes F# Pulumi program
  kubernetes-gcp-csharp                   A C# program to deploy a Kubernetes cluster on Google Cloud
  kubernetes-gcp-go                       A Go program to deploy a Kubernetes cluster on Google Cloud
  kubernetes-gcp-python                   A Python program to deploy a Kubernetes cluster on Google Cloud
  kubernetes-gcp-typescript               A TypeScript program to deploy a Kubernetes cluster on Google Cloud
  kubernetes-gcp-yaml                     A Pulumi YAML program to deploy a Kubernetes cluster on Google Cloud
  kubernetes-go                           A minimal Kubernetes Go Pulumi program
  kubernetes-java                         A minimal Kubernetes Java Pulumi program
  kubernetes-javascript                   A minimal Kubernetes JavaScript Pulumi program
  kubernetes-ovh-csharp                   A C# program to deploy a Kubernetes cluster on OVHcloud
  kubernetes-ovh-go                       A Go program to deploy a Kubernetes cluster on OVHcloud
  kubernetes-ovh-java                     A Java program to deploy a Kubernetes cluster on OVHcloud
  kubernetes-ovh-python                   A Python program to deploy a Kubernetes cluster on OVHcloud
  kubernetes-ovh-typescript               A TypeScript program to deploy a Kubernetes cluster on OVHcloud
  kubernetes-python                       A minimal Kubernetes Python Pulumi program
  kubernetes-typescript                   A minimal Kubernetes TypeScript Pulumi program
  kubernetes-yaml                         A minimal Kubernetes Pulumi YAML program
  linode-go                               A minimal Linode Go Pulumi program
  linode-javascript                       A minimal Linode JavaScript Pulumi program
  linode-python                           A minimal Linode Python Pulumi program
  linode-typescript                       A minimal Linode TypeScript Pulumi program
  linode-yaml                             A minimal Linode Pulumi YAML program
  oci-go                                  A minimal OCI Go Pulumi program
  oci-java                                A minimal Java Pulumi program with Maven builds
  oci-javascript                          A minimal OCI JavaScript Pulumi program
  oci-python                              A minimal OCI Python Pulumi program
  oci-typescript                          A minimal OCI TypeScript Pulumi program
  oci-yaml                                
  openstack-go                            A minimal OpenStack Go Pulumi program
  openstack-javascript                    A minimal OpenStack JavaScript Pulumi program
  openstack-python                        A minimal OpenStack Python Pulumi program
  openstack-typescript                    A minimal OpenStack TypeScript Pulumi program
  openstack-yaml                          A minimal OpenStack Pulumi YAML program
  ovh-csharp                              A minimal OVHcloud C# Pulumi program
  ovh-go                                  A minimal OVHcloud Go Pulumi program
  ovh-java                                A minimal OVHcloud Java Pulumi program
  ovh-python                              A minimal OVHcloud Python Pulumi program
  ovh-typescript                          A minimal OVHcloud TypeScript Pulumi program
  pinecone-csharp                         A minimal Pinecone C# Pulumi program
  pinecone-go                             A minimal Pinecone Go Pulumi program
  pinecone-javascript                     A minimal Pinecone JavaScript Pulumi program
  pinecone-python                         A minimal Pinecone Python Pulumi program
  pinecone-typescript                     A minimal Pinecone TypeScript Pulumi program
  pinecone-yaml                           A minimal Pinecone Pulumi YAML program
  python                                  A minimal Python Pulumi program
  random-csharp                           A minimal Random C# Pulumi program.
  random-go                               A minimal Random Go Pulumi program.
  random-java                             A minimal Random Java Pulumi program
  random-python                           A minimal Random Python Pulumi program.
  random-typescript                       A minimal Random TypeScript Pulumi program.
  random-yaml                             A minimal Random Pulumi YAML program.
  rediscloud-go                           A minimal RedisCloud Pulumi YAML program
  rediscloud-javascript                   A minimal RedisCloud JavaScript Pulumi program
  rediscloud-python                       A minimal RedisCloud Python Pulumi program
  sagemaker-aws-python                    A Python program to deploy a Huggingface LLM model to Amazon SageMaker with CloudWatch monitoring
  scaleway-go                             A minimal Scaleway Go program
  scaleway-javascript                     A minimal Scaleway JavaScript program
  scaleway-python                         A minimal Scaleway Python program
  scaleway-typescript                     A minimal Scaleway TypeScript program
  scaleway-yaml                           A minimal Scaleway Pulumi YAML program
  serverless-aws-csharp                   A C# program to deploy a serverless application on AWS
  serverless-aws-go                       A Go program to deploy a serverless application on AWS
  serverless-aws-python                   A Python program to deploy a serverless application on AWS
  serverless-aws-typescript               A TypeScript program to deploy a serverless application on AWS
  serverless-aws-yaml                     A Pulumi YAML program to deploy a serverless application on AWS
  serverless-azure-csharp                 A C# program to deploy a serverless application on Azure
  serverless-azure-go                     A Go program to deploy a serverless application on Azure
  serverless-azure-python                 A Python program to deploy a serverless application on Azure
  serverless-azure-typescript             A TypeScript program to deploy a serverless application on Azure
  serverless-azure-yaml                   A Pulumi YAML program to deploy a serverless application on Azure
  serverless-gcp-csharp                   A C# program to deploy a serverless application on Google Cloud
  serverless-gcp-go                       A Go program to deploy a serverless application on Google Cloud
  serverless-gcp-python                   A Python program to deploy a serverless application on Google Cloud
  serverless-gcp-typescript               A TypeScript program to deploy a serverless application on Google Cloud
  serverless-gcp-yaml                     A Pulumi YAML program to deploy a serverless application on Google Cloud
  static-website-aws-csharp               A C# program to deploy a static website on AWS
  static-website-aws-go                   A Go program to deploy a static website on AWS
  static-website-aws-python               A Python program to deploy a static website on AWS
  static-website-aws-typescript           A TypeScript program to deploy a static website on AWS
  static-website-aws-yaml                 A Pulumi YAML program to deploy a static website on AWS
  static-website-azure-csharp             A C# program to deploy a static website on Azure
  static-website-azure-go                 A Go program to deploy a static website on Azure
  static-website-azure-python             A Python program to deploy a static website on Azure
  static-website-azure-typescript         A TypeScript program to deploy a static website on Azure
  static-website-azure-yaml               A Pulumi YAML program to deploy a static website on Azure
  static-website-gcp-csharp               A C# program to deploy a static website on Google Cloud
  static-website-gcp-go                   A Go program to deploy a static website on Google Cloud
  static-website-gcp-python               A Python program to deploy a static website on Google Cloud
  static-website-gcp-typescript           A TypeScript program to deploy a static website on Google Cloud
  static-website-gcp-yaml                 A Pulumi YAML program to deploy a static website on Google Cloud
  typescript                              A minimal TypeScript Pulumi program
  visualbasic                             A minimal VB.NET Pulumi program
  vm-aws-csharp                           A C# program to deploy a virtual machine on Amazon EC2
  vm-aws-go                               A Go program to deploy a virtual machine on Amazon EC2
  vm-aws-python                           A Python program to deploy a virtual machine on Amazon EC2
  vm-aws-typescript                       A TypeScript program to deploy a virtual machine on Amazon EC2
  vm-aws-yaml                             A Pulumi YAML program to deploy a virtual machine on Amazon EC2
  vm-azure-csharp                         A C# program to deploy a virtual machine on Azure
  vm-azure-go                             A Go program to deploy a virtual machine on Azure
  vm-azure-python                         A Python program to deploy a virtual machine on Azure
  vm-azure-typescript                     A TypeScript program to deploy a virtual machine on Azure
  vm-azure-yaml                           A Pulumi YAML program to deploy a virtual machine on Azure
  vm-gcp-csharp                           A C# program to deploy a virtual machine on Google Cloud
  vm-gcp-go                               A Go program to deploy a virtual machine on Google Cloud
  vm-gcp-python                           A Python program to deploy a virtual machine on Google Cloud
  vm-gcp-typescript                       A TypeScript program to deploy a virtual machine on Google Cloud
  vm-gcp-yaml                             A Pulumi YAML program to deploy a virtual machine on Google Cloud
  webapp-kubernetes-csharp                A C# program to deploy a web application onto a Kubernetes cluster
  webapp-kubernetes-go                    A Go program to deploy a web application onto a Kubernetes cluster
  webapp-kubernetes-python                A Python program to deploy a web application onto a Kubernetes cluster
  webapp-kubernetes-typescript            A TypeScript program to deploy a web application onto a Kubernetes cluster
  webapp-kubernetes-yaml                  A Pulumi YAML program to deploy a web application onto a Kubernetes cluster
  yaml                                    A minimal Pulumi YAML program
```

</details>
author_association: MEMBER
user: login: kramhuber
created_at: 2025-03-07T16:50:13Z
body: > > @iwahbe could you share a screen grab of what the output of `-l` looks like when there's both org and non-org templates?
> 
> Definitely. It's pretty long at this point:

Thoughts on adding an owner/publisher column (whatever the term is) the user could filter on? I feel like an org will have a lot few internal templates and they'll get lost in the noise of the public templates. Can be follow-on, doesn't need to block this PR
author_association: CONTRIBUTOR
user: login: iwahbe
created_at: 2025-03-07T17:56:47Z
body: > > > @iwahbe could you share a screen grab of what the output of `-l` looks like when there's both org and non-org templates?
> > 
> > 
> > Definitely. It's pretty long at this point:
> 
> Thoughts on adding an owner/publisher column (whatever the term is) the user could filter on? I feel like an org will have a lot few internal templates and they'll get lost in the noise of the public templates. Can be follow-on, doesn't need to block this PR

We should definitely add a `Source` column. I'm not sure what UI we should have there beyond fuzzy search, but we should do that at minimum. 
author_association: MEMBER
user: login: pulumi-bot
created_at: 2025-03-27T15:36:36Z
body: This PR has been shipped in [release v3.159.0](https://github.com/pulumi/pulumi/releases/tag/v3.159.0).
author_association: CONTRIBUTOR