From 9dd6a172a2cec79e82e840acb1464d6de9ececaf Mon Sep 17 00:00:00 2001 From: Aviral Jain Date: Tue, 20 Jan 2026 11:10:06 +0530 Subject: [PATCH] add support to use issue template from registry index for pack yank --- internal/registry/github.go | 91 ++++++++++++++++++++++++++++- internal/registry/registry_cache.go | 6 +- pkg/client/yank_buildpack.go | 28 ++++++++- 3 files changed, 116 insertions(+), 9 deletions(-) diff --git a/internal/registry/github.go b/internal/registry/github.go index 329de29d52..4ee9cd5889 100644 --- a/internal/registry/github.go +++ b/internal/registry/github.go @@ -4,7 +4,10 @@ import ( "bytes" "fmt" "net/url" + "os" "os/exec" + "path/filepath" + "regexp" "strings" "text/template" @@ -18,13 +21,29 @@ type GithubIssue struct { Body string } -func CreateGithubIssue(b Buildpack) (GithubIssue, error) { - titleTemplate, err := template.New("buildpack").Parse(GithubIssueTitleTemplate) +// CreateGithubIssue creates a GitHub issue from a buildpack. If registryRoot is provided, +// it will attempt to load both title and body templates from the registry-index repository, +// falling back to hardcoded templates if the file doesn't exist. +// Returns the issue and a boolean indicating whether templates were loaded from the registry. +func CreateGithubIssue(b Buildpack, registryRoot ...string) (GithubIssue, error) { + titleTemplateStr := GithubIssueTitleTemplate + bodyTemplateStr := GithubIssueBodyTemplate + + // Try to load templates from registry if root is provided + if len(registryRoot) > 0 && registryRoot[0] != "" { + if title, body, err := loadTemplatesFromRegistry(registryRoot[0], b.Yanked); err == nil { + titleTemplateStr = title + bodyTemplateStr = body + } + // Silently fall back to hardcoded templates if loading fails + } + + titleTemplate, err := template.New("buildpack").Parse(titleTemplateStr) if err != nil { return GithubIssue{}, err } - bodyTemplate, err := template.New("buildpack").Parse(GithubIssueBodyTemplate) + bodyTemplate, err := template.New("buildpack").Parse(bodyTemplateStr) if err != nil { return GithubIssue{}, err } @@ -47,6 +66,72 @@ func CreateGithubIssue(b Buildpack) (GithubIssue, error) { }, nil } +// loadTemplatesFromRegistry loads both title and body templates from the registry-index repository. +// It parses the YAML frontmatter to extract the title and extracts the body from the markdown content. +func loadTemplatesFromRegistry(registryRoot string, yanked bool) (string, string, error) { + var templatePath string + if yanked { + templatePath = ".github/ISSUE_TEMPLATE/yank-buildpack.md" + } else { + templatePath = ".github/ISSUE_TEMPLATE/add-buildpack.md" + } + + fullPath := filepath.Join(registryRoot, templatePath) + content, err := os.ReadFile(fullPath) + if err != nil { + return "", "", err + } + + contentStr := string(content) + + // Parse YAML frontmatter to extract title + titleTemplate, bodyTemplate, err := parseTemplateFile(contentStr) + if err != nil { + return "", "", err + } + + // Convert placeholders to Go template syntax + titleTemplate = convertPlaceholdersToGoTemplate(titleTemplate) + bodyTemplate = convertPlaceholdersToGoTemplate(bodyTemplate) + + return titleTemplate, bodyTemplate, nil +} + +// parseTemplateFile parses a GitHub issue template file with YAML frontmatter +// and returns the title template and body template. +func parseTemplateFile(content string) (string, string, error) { + // Split frontmatter from body + parts := strings.SplitN(content, "---\n", 3) + if len(parts) < 3 { + return "", "", errors.New("invalid template format: missing YAML frontmatter") + } + + frontmatter := parts[1] + body := parts[2] + + // Extract title from frontmatter + titleMatch := regexp.MustCompile(`(?m)^title:\s*(.+)$`).FindStringSubmatch(frontmatter) + if len(titleMatch) < 2 { + return "", "", errors.New("invalid template format: missing title in frontmatter") + } + titleTemplate := strings.TrimSpace(titleMatch[1]) + // Remove quotes if present + titleTemplate = strings.Trim(titleTemplate, `"'`) + + return titleTemplate, strings.TrimSpace(body), nil +} + +// convertPlaceholdersToGoTemplate converts template placeholders like {BUILDPACK_ID} and {VERSION} +// to Go template syntax like {{.Namespace}}/{{.Name}} and {{.Version}}. +func convertPlaceholdersToGoTemplate(templateStr string) string { + // Replace {BUILDPACK_ID} with {{.Namespace}}/{{.Name}} + templateStr = strings.ReplaceAll(templateStr, "{BUILDPACK_ID}", "{{.Namespace}}/{{.Name}}") + // Replace {VERSION} with {{.Version}} + templateStr = strings.ReplaceAll(templateStr, "{VERSION}", "{{.Version}}") + + return templateStr +} + func CreateBrowserCmd(browserURL, os string) (*exec.Cmd, error) { _, err := url.ParseRequestURI(browserURL) if err != nil { diff --git a/internal/registry/registry_cache.go b/internal/registry/registry_cache.go index 6ae7e08766..3e111630f2 100644 --- a/internal/registry/registry_cache.go +++ b/internal/registry/registry_cache.go @@ -36,11 +36,7 @@ type Cache struct { } const GithubIssueTitleTemplate = "{{ if .Yanked }}YANK{{ else }}ADD{{ end }} {{.Namespace}}/{{.Name}}@{{.Version}}" -const GithubIssueBodyTemplate = ` -id = "{{.Namespace}}/{{.Name}}" -version = "{{.Version}}" -{{ if .Yanked }}{{ else if .Address }}addr = "{{.Address}}"{{ end }} -` +const GithubIssueBodyTemplate = "```\nid = \"{{.Namespace}}/{{.Name}}\"\nversion = \"{{.Version}}\"\n{{ if .Yanked }}yank = true\n{{ end }}{{ if .Address }}addr = \"{{.Address}}\"\n{{ end }}```" const GitCommitTemplate = `{{ if .Yanked }}YANK{{else}}ADD{{end}} {{.Namespace}}/{{.Name}}@{{.Version}}` // Entry is a list of buildpacks stored in a registry diff --git a/pkg/client/yank_buildpack.go b/pkg/client/yank_buildpack.go index 173f22902c..f3f9528888 100644 --- a/pkg/client/yank_buildpack.go +++ b/pkg/client/yank_buildpack.go @@ -4,6 +4,7 @@ import ( "net/url" "runtime" + "github.com/buildpacks/pack/internal/config" "github.com/buildpacks/pack/internal/registry" ) @@ -36,7 +37,24 @@ func (c *Client) YankBuildpack(opts YankBuildpackOptions) error { Yanked: opts.Yank, } - issue, err := registry.CreateGithubIssue(buildpack) + // Try to get registry cache root to load template from registry-index + var registryRoot string + home, err := config.PackHome() + if err == nil { + registryCache, err := registry.NewRegistryCache(c.logger, home, opts.URL) + if err == nil { + // Initialize the cache if needed (this will clone/refresh the registry) + if err := registryCache.Initialize(); err == nil { + // Refresh to get latest template + _ = registryCache.Refresh() + registryRoot = registryCache.Root + } + } else { + c.logger.Warnf("Error initializing registry cache: %s", err) + } + } + + issue, err := registry.CreateGithubIssue(buildpack, registryRoot) if err != nil { return err } @@ -44,6 +62,14 @@ func (c *Client) YankBuildpack(opts YankBuildpackOptions) error { params := url.Values{} params.Add("title", issue.Title) params.Add("body", issue.Body) + // Add template parameter when we have a registry root (GitHub will use the template if it exists) + if registryRoot != "" { + if opts.Yank { + params.Add("template", "yank-buildpack.md") + } else { + params.Add("template", "add-buildpack.md") + } + } issueURL.RawQuery = params.Encode() c.logger.Debugf("Open URL in browser: %s", issueURL)