diff --git a/changelog/fragments/1755717164-markdown.yaml b/changelog/fragments/1755717164-markdown.yaml
new file mode 100644
index 0000000..4aaad53
--- /dev/null
+++ b/changelog/fragments/1755717164-markdown.yaml
@@ -0,0 +1,55 @@
+# REQUIRED
+# Kind can be one of:
+# - breaking-change: a change to previously-documented behavior
+# - deprecation: functionality that is being removed in a later release
+# - bug-fix: fixes a problem in a previous version
+# - enhancement: extends functionality but does not break or fix existing behavior
+# - feature: new functionality
+# - known-issue: problems that we are aware of in a given version
+# - security: impacts on the security of a product or a user’s deployment.
+# - upgrade: important information for someone upgrading from a prior version
+# - other: does not fit into any of the other categories
+kind: feature
+
+# REQUIRED for all kinds
+# Change summary; a 80ish characters long description of the change.
+summary: Support outputting Markdown files.
+
+# REQUIRED for breaking-change, deprecation, known-issue
+# Long description; in case the summary is not enough to describe the change
+# this field accommodate a description without length limits.
+description: |
+ Updates the `render` command to accept `--file_type` set to either `asciidoc` or `markdown`.
+
+ When set to `asciidoc`, it renders a single AsciiDoc file for a given version using the existing `asciidoc-embedded` template.
+
+ When set to `markdown`, it renders three Markdown files for a given version:
+
+ * Release notes: Creates an `index.md` file using `markdown-index-template`.
+ * Breaking changes: Creates a `breaking.md` file using `markdown-breaking-template`.
+ * Deprecations: Creates a `deprecations.md` file using `markdown-deprecations-template`.
+
+# REQUIRED for breaking-change, deprecation, known-issue
+# impact:
+
+# REQUIRED for breaking-change, deprecation, known-issue
+# action:
+
+# REQUIRED for all kinds
+# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc.
+component:
+
+# AUTOMATED
+# OPTIONAL to manually add other PR URLs
+# PR URL: A link the PR that added the changeset.
+# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added.
+# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number.
+# Please provide it if you are adding a fragment for a different PR.
+pr:
+ - https://github.com/elastic/elastic-agent-changelog-tool/pull/213
+
+# AUTOMATED
+# OPTIONAL to manually add other issue URLs
+# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of).
+# If not present is automatically filled by the tooling with the issue linked to the PR number.
+# issue: https://github.com/owner/repo/1234
diff --git a/cmd/render.go b/cmd/render.go
index 85490fa..51399f5 100644
--- a/cmd/render.go
+++ b/cmd/render.go
@@ -8,21 +8,17 @@ import (
"fmt"
"log"
- "github.com/elastic/elastic-agent-changelog-tool/internal/assets"
"github.com/elastic/elastic-agent-changelog-tool/internal/changelog"
"github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
-var RenderLongDescription = fmt.Sprintf(`Use this command to render the consolidated changelog.
+var RenderLongDescription = `Use this command to render the consolidated changelog.
--version is required. Consolidated changelog version (x.y.z) in 'changelogs' folder
---template is optional. Specify full path to your template file or use predefined templates. Default: asciidoc-embedded
-
-Predefined templates:
-%s
-`, assets.GetEmbeddedTemplates().String())
+--file_type is optional. Specify the file_type: 'asciidoc' or 'markdown'. Default: markdown
+--template is optional. Specify full path to your template file or use predefined templates. Default: asciidoc-embedded`
func RenderCmd(fs afero.Fs) *cobra.Command {
renderCmd := &cobra.Command{
@@ -35,12 +31,18 @@ func RenderCmd(fs afero.Fs) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
dest := viper.GetString("changelog_destination")
renderedDest := viper.GetString("rendered_changelog_destination")
+ repo := viper.GetString("repo")
version, err := cmd.Flags().GetString("version")
if err != nil {
return fmt.Errorf("error parsing flag 'version': %w", err)
}
+ file_type, err := cmd.Flags().GetString("file_type")
+ if err != nil {
+ return fmt.Errorf("error parsing flag 'file_type': %w", err)
+ }
+
template, err := cmd.Flags().GetString("template")
if err != nil {
return fmt.Errorf("error parsing flag 'template': %w", err)
@@ -51,16 +53,36 @@ func RenderCmd(fs afero.Fs) *cobra.Command {
return fmt.Errorf("error loading changelog from file: %w", err)
}
- r := changelog.NewRenderer(fs, c, renderedDest, template)
-
- if err := r.Render(); err != nil {
- return fmt.Errorf("cannot build asciidoc file: %w", err)
+ if file_type == "asciidoc" {
+ r := changelog.NewRenderer(fs, c, renderedDest, "asciidoc-embedded", repo)
+ if err := r.Render(); err != nil {
+ return fmt.Errorf("cannot build asciidoc file: %w", err)
+ }
+ } else if file_type == "markdown" {
+ r_index := changelog.NewRenderer(fs, c, renderedDest, "markdown-index", repo)
+ if err := r_index.Render(); err != nil {
+ return fmt.Errorf("cannot build markdown file: %w", err)
+ }
+ r_breaking := changelog.NewRenderer(fs, c, renderedDest, "markdown-breaking", repo)
+ if err := r_breaking.Render(); err != nil {
+ return fmt.Errorf("cannot build markdown file: %w", err)
+ }
+ r_deprecations := changelog.NewRenderer(fs, c, renderedDest, "markdown-deprecations", repo)
+ if err := r_deprecations.Render(); err != nil {
+ return fmt.Errorf("cannot build markdown file: %w", err)
+ }
+ } else {
+ r := changelog.NewRenderer(fs, c, renderedDest, template, repo)
+ if err := r.Render(); err != nil {
+ return fmt.Errorf("cannot build file: %w", err)
+ }
}
return nil
},
}
+ renderCmd.Flags().String("file_type", viper.GetString("file_type"), "The file type of the rendered release notes: `asciidoc` or `markdown`")
renderCmd.Flags().String("template", viper.GetString("template"), "The template used to generate the changelog")
renderCmd.Flags().String("version", "", "The version of the consolidated changelog being created")
err := renderCmd.MarkFlagRequired("version")
diff --git a/config.changelog.yaml b/config.changelog.yaml
new file mode 100644
index 0000000..82a8bca
--- /dev/null
+++ b/config.changelog.yaml
@@ -0,0 +1,3 @@
+owner: elastic
+repo: elastic-agent-changelog-tool
+rendered_changelog_destination: changelog
diff --git a/config.yaml b/config.yaml
deleted file mode 100644
index c1edfdc..0000000
--- a/config.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-owner: elastic
-repo: elastic-agent-changelog-tool
-template: asciidoc-embedded
-components: [elastic-agent-changelog-tool]
diff --git a/docs/configuration.md b/docs/configuration.md
index 15a099e..f108bf7 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -1,21 +1,29 @@
# Configuration options
-`elastic-agent-changelog-tool` has configuration options available to change it's behaviour.
+`elastic-agent-changelog-tool` has configuration options available to change its behaviour.
-All settings are managed via the [`settings`][settings] package, using [`spf13/viper`][viper].
+All settings are managed via the [`settings`][settings] package, using [`spf13/viper`][viper].
Configurations are bound to environment variables with same name and `ELASTIC_AGENT_CHANGELOG` prefix using [`viper.BindEnv`][bindenv].
This CLI supports and adhere to cross platform XDG Standard provided by [`OpenPeeDeeP/xdg`][xdg].
-|Settings key|Default value|Note|
+| Settings | Default value | Description |
|---|---|---|
-|`fragment_location`|`$GIT_REPO_ROOT/changelog/fragments`|The location of changelog fragments used by the CLI. By default `fragment_root` + `fragment_path`.|
+|`fragment_location`|`$GIT_REPO_ROOT/changelog/fragments`|The location of changelog fragments used by the CLI. By default `fragment_root` + `fragment_path`.|
|`fragment_path`|`changelog/fragments`|The path in `fragment_root` where to locate changelog fragments.|
|`fragment_root`|`$GIT_REPO_ROOT`|The root folder for `fragment_location`.|
## Configuration file
-Not supported yet.
+Add a `config.changelog.yaml` file to the repo where you're generating release notes.
+
+When generating Markdown files, at a minimum you should set the following settings:
+
+| Setting | Default value | Description |
+|---|---|---|
+| `owner` (required) | `elastic` | The owner of the GitHub repo. |
+| `repo` (required) | ‒ | The name of the GitHub repo. |
+| `rendered_changelog_destination` | `changelog` | The directory where you want to put the generated files.
When generating Markdown files, this should probably be `docs/release-notes/_snippets`. |
## Supported Environment Variables
diff --git a/docs/getting-started.md b/docs/getting-started.md
index da0039f..476d040 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -25,6 +25,7 @@ $ elastic-agent-changelog-tool new "my test fragment"
This will create `./changelog/fragments/-my-test-fragment.yaml` with this content:
```yaml
+# REQUIRED
# Kind can be one of:
# - breaking-change: a change to previously-documented behavior
# - deprecation: functionality that is being removed in a later release
@@ -37,25 +38,39 @@ This will create `./changelog/fragments/-my-test-fragment.yaml` with
# - other: does not fit into any of the other categories
kind: feature
+# REQUIRED for all kinds
# Change summary; a 80ish characters long description of the change.
-summary:
+summary: {{.Summary}}
+# REQUIRED for breaking-change, deprecation, known-issue
# Long description; in case the summary is not enough to describe the change
# this field accommodate a description without length limits.
-#description:
+# description:
+# REQUIRED for breaking-change, deprecation, known-issue
+# impact:
+
+# REQUIRED for breaking-change, deprecation, known-issue
+# action:
+
+# REQUIRED for all kinds
# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc.
component:
-# PR URL; optional; the PR number that added the changeset.
+# AUTOMATED
+# OPTIONAL to manually add other PR URLs
+# PR URL: A link the PR that added the changeset.
# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added.
# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number.
# Please provide it if you are adding a fragment for a different PR.
-#pr: https://github.com/owner/repo/1234
+# pr: https://github.com/owner/repo/1234
+# AUTOMATED
+# OPTIONAL to manually add other issue URLs
# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of).
# If not present is automatically filled by the tooling with the issue linked to the PR number.
-#issue: https://github.com/owner/repo/1234
+# issue: https://github.com/owner/repo/1234
+
```
Ensure `kind` is correct and fill the `summary` field with a brief description.
@@ -80,7 +95,7 @@ entries:
kind: feature
pr:
- https://github.com/elastic/elastic-agent-changelog-tool/pull/13
- issue:
+ issue:
- https://github.com/elastic/elastic-agent-changelog-tool/issues/21
timestamp: 1649924282
file:
@@ -93,12 +108,26 @@ There will be multiple entries, one for each files in `changelog/fragments`.
## 5. Render the consolidated changelog
-_Note_: at the moment there is only one renderer implemented: Asciidoc.
+### Markdown
+
+From the root folder of the repository run:
+
+```
+$ elastic-agent-changelog-tool render --version 0.1.0 --file_type markdown
+```
+
+This will create three files:
+
+* `./changelog/0.1.0/index.md`
+* `./changelog/0.1.0/breaking.md`
+* `./changelog/0.1.0/deprecations.md`
+
+### AsciiDoc
From the root folder of the repository run:
```
-$ elastic-agent-changelog-tool render --version 0.1.0
+$ elastic-agent-changelog-tool render --version 0.1.0 --file_type asciidoc
```
This will create `./changelog/0.1.0.asciidoc`.
diff --git a/docs/usage.md b/docs/usage.md
index 790d18b..a76b15a 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -43,11 +43,9 @@ $ elastic-agent-changelog-tool build --version=next --owner --repo
+$ elastic-agent-changelog-tool render --version=next --file_type
```
-The template value can be chosen from a predefined internal list of templates (`render --help`) or use a full path to your template file.
-
An example is [`../changelog/0.1.0.yaml`](../changelog/0.1.0.yaml).
### My PR does not need a changelog
@@ -82,9 +80,8 @@ $ elastic-agent-changelog-tool build --version=next --owner --repo
+$ elastic-agent-changelog-tool render --version=next --file_type
```
-The template value can be chosen from a predefined internal list of templates (`render --help`) or use a full path to your template file.
An example is [`../changelog/0.1.0.yaml`](../changelog/0.1.0.yaml).
@@ -97,8 +94,14 @@ The side effect is that the changelog will include all entries from latest stabl
1. Create consolidated changelog with `$ elastic-agent-changelog-tool build --version --owner --repo `;
* This will create `./changelog/x.y.z.yaml`;
-2. Create rendered changelog with `$ elastic-agent-changelog-tool render --version --template `;
-* This will generate an asciidoc file in the `changelog/` directory;
+2. Create rendered changelog with `$ elastic-agent-changelog-tool render --version --file_type `;
+
+ Depending on the specified `file_type`, this will generate the following files:
+ * `markdown`:
+ * Release notes: `./changelog//index.md`
+ * Breaking changes: `./changelog//breaking.md`
+ * Deprecations: `./changelog//deprecations.md`
+ * `asciidoc`: `changelog/.asciidoc`
3. Use the rendered changelog.
**Note**: we do not remove fragments, as they will be needed for the stable release version changelog.
@@ -111,30 +114,39 @@ The side effect is that the changelog will include all entries from latest stabl
These steps require [GitHub Authentication](./github-authentication.md).
-* Wait for the last BC of the release. If another BC is generated after that or a patch version for a previous minor is released, you might need to restart the process.
-* Create a branch **from the commit of the BC**.
-* From the root folder of the repository run:
-
-```
-$ elastic-agent-changelog-tool build --version x.y.z --owner --repo
-```
-* Where:
- * `x.y.z` is the version to release.
- * `owner` is the user / organization the repository to use belongs to. The default value is `elastic`.
- * `repo` is the name of the repository containing the issues / PRs, etc. The default value is `elastic-agent`.
-* This will create `./changelog/x.y.z.yaml`.
-* From the root of the repository run:
-```
-$ elastic-agent-changelog-tool cleanup
-```
-* Commit the previous changes (consolidated changelod and removed files)
-* From the root folder of the repository run:
-```
-$ elastic-agent-changelog-tool render --version x.y.z --template
-```
-* This will generate an asciidoc fragment in the `changelog/` directory.
-* Integrate the generated fragment into the changelog. If the changelog is stored in the same repository, commit the changes in this same branch.
-* Create a PR with the changes to the `x.y` branch.
+1. Wait for the last BC of the release. If another BC is generated after that or a patch version for a previous minor is released, you might need to restart the process.
+1. Create a branch **from the commit of the BC**.
+1. From the root folder of the repository run:
+
+ ```
+ $ elastic-agent-changelog-tool build --version x.y.z --owner --repo
+ ```
+
+ Where:
+
+ * `x.y.z` is the version to release.
+ * `owner` is the user / organization the repository to use belongs to. The default value is `elastic`.
+ * `repo` is the name of the repository containing the issues / PRs, etc. The default value is `elastic-agent`.
+
+ This will create `./changelog/x.y.z.yaml`.
+1. From the root of the repository run:
+ ```
+ $ elastic-agent-changelog-tool cleanup
+ ```
+1. Commit the previous changes (consolidated changelod and removed files)
+1. From the root folder of the repository run:
+ ```
+ $ elastic-agent-changelog-tool render --version x.y.z --file_type
+ ```
+
+ Depending on the specified `file_type`, this will generate the following files:
+ * `markdown`:
+ * Release notes: `./changelog//index.md`
+ * Breaking changes: `./changelog//breaking.md`
+ * Deprecations: `./changelog//deprecations.md`
+ * `asciidoc`: `changelog/.asciidoc`
+1. Integrate the generated fragment into the changelog. If the changelog is stored in the same repository, commit the changes in this same branch.
+1. Create a PR with the changes to the `x.y` branch.
### On Release Day
diff --git a/go.mod b/go.mod
index ac9f6a5..c0adc82 100644
--- a/go.mod
+++ b/go.mod
@@ -13,7 +13,6 @@ require (
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.10.0
golang.org/x/oauth2 v0.24.0
- golang.org/x/text v0.21.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -40,6 +39,7 @@ require (
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
+ golang.org/x/text v0.21.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
diff --git a/internal/assets/assets.go b/internal/assets/assets.go
index 955ff42..7b978f1 100644
--- a/internal/assets/assets.go
+++ b/internal/assets/assets.go
@@ -14,13 +14,25 @@ import (
// These strings can be used in the config template field or renderer template flag
func GetEmbeddedTemplates() embeddedTemplates {
return map[string]string{
- "asciidoc-embedded": "asciidoc-template.asciidoc",
+ "asciidoc-embedded": "asciidoc-template.asciidoc",
+ "markdown-index": "markdown-index-template.md.tmpl",
+ "markdown-breaking": "markdown-breaking-template.md.tmpl",
+ "markdown-deprecations": "markdown-deprecations-template.md.tmpl",
}
}
//go:embed asciidoc-template.asciidoc
var AsciidocTemplate embed.FS
+//go:embed markdown-index-template.md.tmpl
+var MarkdownIndexTemplate embed.FS
+
+//go:embed markdown-breaking-template.md.tmpl
+var MarkdownBreakingTemplate embed.FS
+
+//go:embed markdown-deprecations-template.md.tmpl
+var MarkdownDeprecationsTemplate embed.FS
+
type embeddedTemplates map[string]string
func (t embeddedTemplates) String() string {
diff --git a/internal/assets/markdown-breaking-template.md.tmpl b/internal/assets/markdown-breaking-template.md.tmpl
new file mode 100644
index 0000000..5a2db49
--- /dev/null
+++ b/internal/assets/markdown-breaking-template.md.tmpl
@@ -0,0 +1,14 @@
+## {{.Version}} [{{.Repo}}-{{.Version}}-breaking-changes]
+{{ if .BreakingChange -}}{{ range $k, $v := .BreakingChange }}{{ range $item := $v }}
+::::{dropdown} {{ $item.Summary | beautify }}
+{{ if $item.Description }}{{ $item.Description }}{{ end }}
+
+For more information, check {{ linkPRSource $item.Component $item.LinkedPR }}{{ linkIssueSource $item.Component $item.LinkedIssue }}.
+
+{{ if not $item.Impact }}% {{ end }}**Impact**
{{ if $item.Impact }}{{ $item.Impact }}{{ else }}_Add a description of the impact_{{ end }}
+
+{{ if not $item.Action }}% {{ end }}**Action**
{{ if $item.Action }}{{ $item.Action }}{{ else }}_Add a description of the what action to take_{{ end }}
+::::
+{{- end }}{{- end }}{{ else }}
+_No breaking changes._
+{{- end }}
diff --git a/internal/assets/markdown-deprecations-template.md.tmpl b/internal/assets/markdown-deprecations-template.md.tmpl
new file mode 100644
index 0000000..cf5d066
--- /dev/null
+++ b/internal/assets/markdown-deprecations-template.md.tmpl
@@ -0,0 +1,16 @@
+## {{.Version}} [{{.Repo}}-{{.Version}}-deprecations]
+{{ if .Deprecation -}}{{ range $k, $v := .Deprecation }}{{ range $item := $v }}
+
+::::{dropdown} {{ $item.Summary | beautify }}
+{{ if $item.Description }}{{ $item.Description }}{{ end }}
+
+For more information, check {{ linkPRSource $item.Component $item.LinkedPR }}{{ linkIssueSource $item.Component $item.LinkedIssue }}.
+
+{{ if not $item.Impact }}% {{ end }}**Impact**
{{ if $item.Impact }}{{ $item.Impact }}{{ else }}_Add a description of the impact_{{ end }}
+
+{{ if not $item.Action }}% {{ end }}**Action**
{{ if $item.Action }}{{ $item.Action }}{{ else }}_Add a description of the what action to take_{{ end }}
+::::
+{{- end }}{{- end }}
+{{ else }}
+_No deprecations._
+{{- end }}
diff --git a/internal/assets/markdown-index-template.md.tmpl b/internal/assets/markdown-index-template.md.tmpl
new file mode 100644
index 0000000..3ea38be
--- /dev/null
+++ b/internal/assets/markdown-index-template.md.tmpl
@@ -0,0 +1,30 @@
+## {{.Version}} [{{.Repo}}-release-notes-{{.Version}}]
+{{ if or .KnownIssue .BreakingChange .Deprecation }}
+{{ other_links }}{{- end }}
+{{ if or .Feature .Enhancement .Security .BugFix }}{{ if or .Feature .Enhancement }}
+### Features and enhancements [{{.Repo}}-{{.Version}}-features-enhancements]
+{{ if .Feature }}{{ range $k, $v := .Feature }}{{ range $item := $v }}
+* {{ $item.Summary | beautify }} {{ linkPRSource $item.Component $item.LinkedPR }} {{ linkIssueSource $item.Component $item.LinkedIssue }}{{ if $item.Description }}
+{{ $item.Description | indent }}
+{{- end }}
+{{- end }}{{- end }}{{- end }}{{ if .Enhancement }}{{ range $k, $v := .Enhancement }}{{ range $item := $v }}
+* {{ $item.Summary | beautify }} {{ linkPRSource $item.Component $item.LinkedPR }} {{ linkIssueSource $item.Component $item.LinkedIssue }}{{ if $item.Description }}
+{{ $item.Description | indent }}
+{{- end }}
+{{- end }}{{- end }}{{- end }}
+{{- end }}
+
+{{ if or .Security .BugFix }}
+### Fixes [{{.Repo}}-{{.Version}}-fixes]
+{{ if .Security }}{{ range $k, $v := .Security }}{{ range $item := $v }}
+* {{ $item.Summary | beautify }} {{ linkPRSource $item.Component $item.LinkedPR }} {{ linkIssueSource $item.Component $item.LinkedIssue }}{{ if $item.Description }}
+{{ $item.Description | indent }}
+{{- end }}
+{{- end }}{{- end }}{{- end }}{{ if .BugFix }}{{ range $k, $v := .BugFix }}{{ range $item := $v }}
+* {{ $item.Summary | beautify }} {{ linkPRSource $item.Component $item.LinkedPR }} {{ linkIssueSource $item.Component $item.LinkedIssue }}{{ if $item.Description }}
+{{ $item.Description | indent }}
+{{- end }}
+{{- end }}{{- end }}{{- end }}{{- end }}
+{{ else }}
+_No new features, enhancements, or fixes._
+{{- end }}
diff --git a/internal/changelog/entry.go b/internal/changelog/entry.go
index 95fde7a..c0a7fe2 100644
--- a/internal/changelog/entry.go
+++ b/internal/changelog/entry.go
@@ -20,6 +20,8 @@ type Entry struct {
Component string `yaml:"component"`
LinkedPR []string `yaml:"pr"`
LinkedIssue []string `yaml:"issue"`
+ Impact string `yaml:"impact"`
+ Action string `yaml:"action"`
Timestamp int64 `yaml:"timestamp"`
File FragmentFileInfo `yaml:"file"`
@@ -35,6 +37,8 @@ func EntryFromFragment(f fragment.File) Entry {
Component: f.Fragment.Component,
LinkedPR: []string{},
LinkedIssue: []string{},
+ Impact: f.Fragment.Impact,
+ Action: f.Fragment.Action,
Timestamp: f.Timestamp,
File: FragmentFileInfo{
Name: f.Name,
diff --git a/internal/changelog/fragment/creator_internal_test.go b/internal/changelog/fragment/creator_internal_test.go
index f3f398b..84bd1f3 100644
--- a/internal/changelog/fragment/creator_internal_test.go
+++ b/internal/changelog/fragment/creator_internal_test.go
@@ -74,7 +74,8 @@ func TestCreate(t *testing.T) {
content, err := afero.ReadFile(fc.fs, path.Join(fc.location, fc.filename("foobar")))
require.Nil(t, err)
- expected := `# Kind can be one of:
+ expected := `# REQUIRED
+# Kind can be one of:
# - breaking-change: a change to previously-documented behavior
# - deprecation: functionality that is being removed in a later release
# - bug-fix: fixes a problem in a previous version
@@ -86,26 +87,38 @@ func TestCreate(t *testing.T) {
# - other: does not fit into any of the other categories
kind: feature
+# REQUIRED for all kinds
# Change summary; a 80ish characters long description of the change.
summary: foobar
+# REQUIRED for breaking-change, deprecation, known-issue
# Long description; in case the summary is not enough to describe the change
# this field accommodate a description without length limits.
-# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment.
-#description:
+# description:
+# REQUIRED for breaking-change, deprecation, known-issue
+# impact:
+
+# REQUIRED for breaking-change, deprecation, known-issue
+# action:
+
+# REQUIRED for all kinds
# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc.
component:
-# PR URL; optional; the PR number that added the changeset.
+# AUTOMATED
+# OPTIONAL to manually add other PR URLs
+# PR URL: A link the PR that added the changeset.
# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added.
# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number.
# Please provide it if you are adding a fragment for a different PR.
-#pr: https://github.com/owner/repo/1234
+# pr: https://github.com/owner/repo/1234
+# AUTOMATED
+# OPTIONAL to manually add other issue URLs
# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of).
# If not present is automatically filled by the tooling with the issue linked to the PR number.
-#issue: https://github.com/owner/repo/1234
+# issue: https://github.com/owner/repo/1234
`
got := string(content)
assert.Equal(t, expected, got, `This test exists to force review on changes to the Changelog Fragment template, as changing the template may introduce breaking changes.
diff --git a/internal/changelog/fragment/fragment.go b/internal/changelog/fragment/fragment.go
index 6e717f7..2546347 100644
--- a/internal/changelog/fragment/fragment.go
+++ b/internal/changelog/fragment/fragment.go
@@ -11,4 +11,6 @@ type Fragment struct {
Component string `yaml:"component"`
Pr string `yaml:"pr"`
Issue string `yaml:"issue"`
+ Impact string `yaml:"impact"`
+ Action string `yaml:"action"`
}
diff --git a/internal/changelog/fragment/template.yaml b/internal/changelog/fragment/template.yaml
index 05b5332..85f5565 100644
--- a/internal/changelog/fragment/template.yaml
+++ b/internal/changelog/fragment/template.yaml
@@ -1,3 +1,4 @@
+# REQUIRED
# Kind can be one of:
# - breaking-change: a change to previously-documented behavior
# - deprecation: functionality that is being removed in a later release
@@ -10,23 +11,35 @@
# - other: does not fit into any of the other categories
kind: feature
+# REQUIRED for all kinds
# Change summary; a 80ish characters long description of the change.
summary: {{.Summary}}
+# REQUIRED for breaking-change, deprecation, known-issue
# Long description; in case the summary is not enough to describe the change
# this field accommodate a description without length limits.
-# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment.
-#description:
+# description:
+# REQUIRED for breaking-change, deprecation, known-issue
+# impact:
+
+# REQUIRED for breaking-change, deprecation, known-issue
+# action:
+
+# REQUIRED for all kinds
# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc.
component:
-# PR URL; optional; the PR number that added the changeset.
+# AUTOMATED
+# OPTIONAL to manually add other PR URLs
+# PR URL: A link the PR that added the changeset.
# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added.
# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number.
# Please provide it if you are adding a fragment for a different PR.
-#pr: https://github.com/owner/repo/1234
+# pr: https://github.com/owner/repo/1234
+# AUTOMATED
+# OPTIONAL to manually add other issue URLs
# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of).
# If not present is automatically filled by the tooling with the issue linked to the PR number.
-#issue: https://github.com/owner/repo/1234
+# issue: https://github.com/owner/repo/1234
diff --git a/internal/changelog/renderer.go b/internal/changelog/renderer.go
index 48ea049..c56a5b8 100644
--- a/internal/changelog/renderer.go
+++ b/internal/changelog/renderer.go
@@ -9,14 +9,14 @@ import (
"fmt"
"html/template"
"log"
+ "os"
"path"
+ "regexp"
"strings"
"github.com/elastic/elastic-agent-changelog-tool/internal/assets"
"github.com/spf13/afero"
"github.com/spf13/viper"
- "golang.org/x/text/cases"
- "golang.org/x/text/language"
)
type Renderer struct {
@@ -25,19 +25,21 @@ type Renderer struct {
// dest is the destination location where the changelog is written to
dest string
templ string
+ repo string
}
-func NewRenderer(fs afero.Fs, c Changelog, dest string, templ string) *Renderer {
+func NewRenderer(fs afero.Fs, c Changelog, dest string, templ string, repo string) *Renderer {
return &Renderer{
changelog: c,
fs: fs,
dest: dest,
templ: templ,
+ repo: repo,
}
}
func (r Renderer) Render() error {
- log.Printf("render changelog for version: %s\n", r.changelog.Version)
+ log.Printf("render %s for version: %s\n", r.templ, r.changelog.Version)
tpl, err := r.Template()
if err != nil {
@@ -47,30 +49,44 @@ func (r Renderer) Render() error {
type TemplateData struct {
Component string
Version string
+ Repo string
Changelog Changelog
Kinds map[Kind]bool
+ // In Markdown, this goes to release notes
+ Enhancement map[string][]Entry
+ Feature map[string][]Entry
+ Security map[string][]Entry
+ BugFix map[string][]Entry
+ // In Markdown, this goes to breaking changes
BreakingChange map[string][]Entry
- Deprecation map[string][]Entry
- BugFix map[string][]Entry
- Enhancement map[string][]Entry
- Feature map[string][]Entry
- KnownIssue map[string][]Entry
- Security map[string][]Entry
- Upgrade map[string][]Entry
- Other map[string][]Entry
+ // In Markdown, this goes to deprecations
+ Deprecation map[string][]Entry
+ // In Markdown, this goes to known issues
+ KnownIssue map[string][]Entry
+ // In Markdown... TBD
+ Upgrade map[string][]Entry
+ Other map[string][]Entry
}
td := TemplateData{
- buildTitleByComponents(r.changelog.Entries), r.changelog.Version, r.changelog,
+ buildTitleByComponents(r.changelog.Entries),
+ r.changelog.Version,
+ r.repo,
+ r.changelog,
collectKinds(r.changelog.Entries),
- collectByKindMap(r.changelog.Entries, BreakingChange),
- collectByKindMap(r.changelog.Entries, Deprecation),
- collectByKindMap(r.changelog.Entries, BugFix),
+ // In Markdown, this goes to release notes
collectByKindMap(r.changelog.Entries, Enhancement),
collectByKindMap(r.changelog.Entries, Feature),
- collectByKindMap(r.changelog.Entries, KnownIssue),
collectByKindMap(r.changelog.Entries, Security),
+ collectByKindMap(r.changelog.Entries, BugFix),
+ // In Markdown, this goes to breaking changes
+ collectByKindMap(r.changelog.Entries, BreakingChange),
+ // In Markdown, this goes to deprecations
+ collectByKindMap(r.changelog.Entries, Deprecation),
+ // In Markdown, this goes to known issues
+ collectByKindMap(r.changelog.Entries, KnownIssue),
+ // In Markdown... TBD
collectByKindMap(r.changelog.Entries, Upgrade),
collectByKindMap(r.changelog.Entries, Other),
}
@@ -81,42 +97,77 @@ func (r Renderer) Render() error {
return strings.Join(ids, "-")
},
// nolint:staticcheck // ignoring for now, supports for multiple component is not implemented
- "linkPRSource": func(component string, ids []string) string {
+ "linkPRSource": func(repo string, ids []string) string {
res := make([]string, len(ids))
-
for i, id := range ids {
- res[i] = fmt.Sprintf("{%s-pull}%v[#%v]", component, id, id)
+ res[i] = getLink(id, r.repo, "pull", r.templ)
}
-
return strings.Join(res, " ")
},
// nolint:staticcheck // ignoring for now, supports for multiple component is not implemented
- "linkIssueSource": func(component string, ids []string) string {
+ "linkIssueSource": func(repo string, ids []string) string {
res := make([]string, len(ids))
-
for i, id := range ids {
- res[i] = fmt.Sprintf("{%s-issue}%v[#%v]", component, id, id)
+ res[i] = getLink(id, r.repo, "issues", r.templ)
}
-
return strings.Join(res, " ")
},
// Capitalize sentence and ensure ends with .
- "beautify": func(s1 string) string {
- s2 := strings.Builder{}
- s2.WriteString(cases.Title(language.English).String(s1))
- if !strings.HasSuffix(s1, ".") {
- s2.WriteString(".")
+ "beautify": func(s string) string {
+ if s == "" {
+ return ""
+ }
+ s = strings.ToUpper(string(s[0])) + s[1:]
+ if !strings.HasSuffix(s, ".") {
+ s += "."
+ }
+ return s
+ },
+ // Indent lines
+ "indent": func(s string) string {
+ re := regexp.MustCompile(`\n|\r|^`)
+ return re.ReplaceAllString(s, "\n ")
+ },
+ "other_links": func() string {
+ var links []string
+ if len(td.KnownIssue) > 0 {
+ links = append(
+ links,
+ "[Known issues](/release-notes/known-issues.md)",
+ )
+ }
+ if len(td.BreakingChange) > 0 {
+ links = append(
+ links,
+ fmt.Sprintf(
+ "[Breaking changes](/release-notes/breaking-changes.md#%s-%s-breaking-changes)",
+ r.repo,
+ r.changelog.Version,
+ ),
+ )
+ }
+ if len(td.Deprecation) > 0 {
+ links = append(
+ links,
+ fmt.Sprintf(
+ "[Deprecations](/release-notes/deprecations.md#%s-%s-deprecations)",
+ r.repo,
+ r.changelog.Version,
+ ),
+ )
+ }
+ if len(links) > 0 {
+ return fmt.Sprintf(
+ "_This release also includes: %s._",
+ strings.Join(links, " and"),
+ )
+ } else {
+ return ""
}
- return s2.String()
},
// Ensure components have section styling
"header2": func(s1 string) string {
- s2 := strings.Builder{}
- s2.WriteString(s1)
- if !strings.HasSuffix(s1, "::") && s1 != "" {
- s2.WriteString("::")
- }
- return s2.String()
+ return fmt.Sprintf("**%s**", s1)
},
}).
Parse(string(tpl))
@@ -131,10 +182,25 @@ func (r Renderer) Render() error {
panic(err)
}
- outFile := path.Join(r.dest, fmt.Sprintf("%s.asciidoc", r.changelog.Version))
- log.Printf("saving changelog in %s\n", outFile)
-
- return afero.WriteFile(r.fs, outFile, data.Bytes(), changelogFilePerm)
+ outFile := func(template string) string {
+ if template == "markdown-index" {
+ return path.Join(r.dest, r.changelog.Version, "index.md")
+ } else if template == "markdown-breaking" {
+ return path.Join(r.dest, r.changelog.Version, "breaking.md")
+ } else if template == "markdown-deprecations" {
+ return path.Join(r.dest, r.changelog.Version, "deprecations.md")
+ } else {
+ return path.Join(r.dest, fmt.Sprintf("%s.asciidoc", r.changelog.Version))
+ }
+ }
+ if r.templ != "asciidoc-embedded" {
+ dir := path.Join(r.dest, r.changelog.Version)
+ err := os.MkdirAll(dir, os.ModePerm)
+ if err != nil {
+ return fmt.Errorf("error creating directory %s: %v", dir, err)
+ }
+ }
+ return afero.WriteFile(r.fs, outFile(r.templ), data.Bytes(), changelogFilePerm)
}
func (r Renderer) Template() ([]byte, error) {
@@ -142,11 +208,18 @@ func (r Renderer) Template() ([]byte, error) {
var err error
if embeddedFileName, ok := assets.GetEmbeddedTemplates()[r.templ]; ok {
- data, err = assets.AsciidocTemplate.ReadFile(embeddedFileName)
+ if r.templ == "markdown-index" {
+ data, err = assets.MarkdownIndexTemplate.ReadFile(embeddedFileName)
+ } else if r.templ == "markdown-breaking" {
+ data, err = assets.MarkdownBreakingTemplate.ReadFile(embeddedFileName)
+ } else if r.templ == "markdown-deprecations" {
+ data, err = assets.MarkdownDeprecationsTemplate.ReadFile(embeddedFileName)
+ } else if r.templ == "asciidoc-embedded" {
+ data, err = assets.AsciidocTemplate.ReadFile(embeddedFileName)
+ }
if err != nil {
return []byte{}, fmt.Errorf("cannot read embedded template: %s %w", embeddedFileName, err)
}
-
return data, nil
}
@@ -158,6 +231,21 @@ func (r Renderer) Template() ([]byte, error) {
return data, nil
}
+func getLink(id string, repo string, ghType string, templ string) string {
+ re := regexp.MustCompile(`\d+$`)
+ number := re.FindString(id)
+ if id == number {
+ id = fmt.Sprintf("https://github.com/elastic/%s/%s/%s", repo, ghType, id)
+ }
+ if templ == "asciidoc-embedded" {
+ // Format as AsciiDoc links
+ return fmt.Sprintf("%s[#%s]", id, number)
+ } else {
+ // Format as Markdown links
+ return fmt.Sprintf("[#%s](%s)", number, id)
+ }
+}
+
func collectKinds(items []Entry) map[Kind]bool {
// NOTE: collect kinds in a set-like map to avoid duplicates
kinds := map[Kind]bool{}
diff --git a/internal/changelog/renderer_test.go b/internal/changelog/renderer_test.go
index 33b5f3f..a9f5f81 100644
--- a/internal/changelog/renderer_test.go
+++ b/internal/changelog/renderer_test.go
@@ -34,7 +34,7 @@ func TestRenderer(t *testing.T) {
c, err := changelog.FromFile(fs, inFile)
require.NoError(t, err)
- r := changelog.NewRenderer(fs, c, dest, "asciidoc-embedded")
+ r := changelog.NewRenderer(fs, c, dest, "asciidoc-embedded", "elastic-agent")
err = r.Render()
require.Nil(t, err)
diff --git a/internal/settings/settings.go b/internal/settings/settings.go
index be6b5e6..4dc335c 100644
--- a/internal/settings/settings.go
+++ b/internal/settings/settings.go
@@ -25,7 +25,7 @@ func Init() {
setConstants()
viper.AddConfigPath(viper.GetString("config_file"))
- viper.SetConfigName("config")
+ viper.SetConfigName("config.changelog.yaml")
viper.SetConfigType("yaml")
// TODO: better error handling (skip missing file error)
@@ -60,7 +60,7 @@ func setDefaults() {
viper.SetDefault("changelog_destination", "changelog")
viper.SetDefault("rendered_changelog_destination", "changelog")
-
+ viper.SetDefault("file_type", "markdown")
viper.SetDefault("template", "asciidoc-embedded")
}