README
¶
Changelog
Any repository set up with the Solo bot webhook for CI and release builds can opt into using the automated changelog provided in this utility. The benefits of using the changelog include:
- Automatically producing the description for the github release page, and optionally the docs, with zero effort.
- Ensuring that every change is described in the release notes, with links to Github issues.
- Ensuring that the release notes are consistently formatted across releases and repos.
- Ensuring that the release versions are incremented correctly according to semantic versioning.
Turning on Changelog
Add a top-level directory called "changelog" in the repo. This directory will ultimately contain a structure like the following:
changelog/
v0.1.0/
summary.md
foo.yaml
bar.yaml
v0.2.0/
foo2.yaml
v0.2.1/
foo3.yaml
Here, v0.1.0
, v0.2.0
, and v0.2.1
represent released or unreleased versions of the project. The bot will
check to make sure there is exactly one directory corresponding to an unreleased version
(greater than the latest release tag) and that at least one new changelog file has been added.
Changelog files
A changelog file contains a list of changelog entries, like this:
changelog:
- type: NEW_FEATURE
description: Gloo releases now automatically publish a changelog to the docs.
issueLink: https://github.com/solo-io/gloo/issues/465
- ...
- ...
Type must be one of NEW_FEATURE
, FIX
, BREAKING_CHANGE
, DEPENDENCY_BUMP
, HELM
, UPGRADE
, or NON_USER_FACING
.
Changelog entries that are not of type NON_USER_FACING
or DEPENDENCY_BUMP
must have a description and an issue link.
Those fields are optional for NON_USER_FACING
and DEPENDENCY_BUMP
changes.
DEPENDENCY_BUMP
changes have a few additional required fields: dependencyTag
, dependencyOwner
, and dependencyRepo
. For example,
this is a valid changelog file containing a dependency bump:
changelog:
- type: DEPENDENCY_BUMP
description: Bumped the version of go-utils to pick up the new changelog feature.
dependencyOwner: solo-io
dependencyRepo: go-utils
dependencyTag: v0.6.2
Changelog entries can set an optional boolean field called resolvesIssue
. This can be used by CI
systems to automatically close issues linked to the changelog. The default value for this field is true
.
The description field should be one or more complete sentences (starting with a capital letter, ending with a period). The issue link should point to a valid github URL. These conventions are currently not validated, but may be in a future version.
The name of the changelog filename does not matter. It is useful to pick a unique name for the PR,
to avoid potential merge conflicts. For instance, you may add this change in a file called
publish_changelogs.yaml
. As long as it is valid yaml in the correct tag directory, it will be
considered valid.
Special files: summary and closing
There are two special files that can be added to assist with changelog rendering. These are:
summary.md
and closing.md
. These files can be added or modified at any time, and should
contain valid markdown. When the changelog is rendered, the summary will be included at the
top, before the list of changes, and the closing notes will be included at the end.
Changelog validation
When changelogs are enabled, PRs must include a valid changelog file or they will fail verification and cannot be merged.
The changelog file must be included in the correct directory according to semver. For example, if the
last released version was v0.2.5
, new PRs that don't create breaking changes should add changelog
files into v0.2.6
(they should create the directory if it does not yet exist). If the PR contains
a breaking change entry, it should create or rename the existing directory to v0.3.0
.
For projects that have already released v1.0.0
, breaking changes should increment the major version
instead (v2.0.0
). Non-breaking changes should increment the minor version (v1.1.0
).
Releasing a stable v1.0 version
There is one special case for incrementing versions: publishing a stable 1.0 API. This can be done
by setting the releaseStableApi
field to true.
changelog:
- ...
. ...
releaseStableApi: true
Publishing release notes to Github
Changelogs will automatically be rendered into a markdown string, and the CI release bot will immediately update a release description to be the changelog when a release is published. No manually entered description should be used (it will get overwritten).
Rendering notes
Changelogs for a tag are merged and rendered in the following order:
- Summary
- Dependency bumps
- Breaking changes
- Upgrade notes
- Helm changes
- New Features
- Fixes
- Closing
If the contents for a section are empty, it is omitted.
A breaking change, upgrade note, helm change, new feature, or fix are rendered in the following way: <description> (<issueLink>)
Non-user facing changes are omitted from the changelog. If there are no user-facing changes in a release, the rendered notes will just say:
This release contained no user-facing changes.
Pushing release notes and docs to Solo Docs
This changelog can be pushed automatically to the docs using the PushDocsCli.
Documentation
¶
Index ¶
- Constants
- Variables
- func ChangelogDirExists(fs afero.Fs, changelogParentPath string) (bool, error)
- func GenerateChangelogForTags(ctx context.Context, tags []string, reader ChangelogReader, w io.Writer) error
- func GenerateChangelogFromLocalDirectory(ctx context.Context, repoRootPath, owner, repo, changelogDirPath string, ...) error
- func GenerateChangelogMarkdown(changelog *Changelog) string
- func GetChangelogFilesAdded(ctx context.Context, client githubutils.RepoClient, base, sha string) ([]github.CommitFile, error)
- func GetChangelogMarkdownForPR(owner, repo string) (string, error)
- func GetLatestTag(ctx context.Context, owner, repo string) (string, error)
- func GetProposedTag(fs afero.Fs, latestTag, changelogParentPath string) (string, error)
- func GetProposedTagForRepo(ctx context.Context, client *github.Client, owner, repo string) (string, error)deprecated
- func GetValidationSettingsPath() string
- func IsInvalidDirectoryNameError(err error) bool
- func IsKnownChangelogFile(path string) bool
- func IsMultipleVersionsFoundError(err error) bool
- func IsNoVersionFoundError(err error) bool
- func RefHasChangelog(ctx context.Context, client *github.Client, owner, repo, sha string) (bool, error)deprecated
- type Changelog
- type ChangelogEntry
- type ChangelogEntryType
- type ChangelogFile
- type ChangelogList
- type ChangelogReader
- type ChangelogTmplData
- type ChangelogValidator
- type TagComparator
- type ValidationSettings
Constants ¶
const (
// all functions in this file that use ChangelogDirectory are marked deprecated. The hope
// is we don't need to account for the "ActiveSubdirectory" setting which was added _after_ said deprecation
ChangelogDirectory = "changelog"
SummaryFile = "summary.md"
ClosingFile = "closing.md"
)
const (
MasterBranch = "master"
ValidationSettingsFile = "validation.yaml"
)
Variables ¶
var (
MountLocalDirectoryError = func(err error) error {
return errors.Wrapf(err, "unable to mount local directory")
}
ReadChangelogDirError = func(err error) error {
return errors.Wrapf(err, "unable to read changelog directory")
}
GetChangelogForTagError = func(err error) error {
return errors.Wrapf(err, "unable to get changelog for tag")
}
GenerateChangelogSummaryTemplateError = func(err error) error {
return errors.Wrapf(err, "unable to generate changelog summary from template")
}
)
var (
UnableToListFilesError = func(err error, directory string) error {
return errors.Wrapf(err, "Unable to list files in directory %s", directory)
}
UnexpectedDirectoryError = func(name, directory string) error {
return eris.Errorf("Unexpected directory %s in changelog directory %s", name, directory)
}
UnableToReadSummaryFileError = func(err error, path string) error {
return errors.Wrapf(err, "Unable to read summary file %s", path)
}
UnableToReadClosingFileError = func(err error, path string) error {
return errors.Wrapf(err, "Unable to read closing file %s", path)
}
NoEntriesInChangelogError = func(filename string) error {
return eris.Errorf("No changelog entries found in file %s.", filename)
}
UnableToParseChangelogError = func(err error, path string) error {
return errors.Wrapf(err, "File %s is not a valid changelog file.", path)
}
MissingIssueLinkError = eris.Errorf("Changelog entries must have an issue link")
MissingDescriptionError = eris.Errorf("Changelog entries must have a description")
MissingOwnerError = eris.Errorf("Dependency bumps must have an owner")
MissingRepoError = eris.Errorf("Dependency bumps must have a repo")
MissingTagError = eris.Errorf("Dependency bumps must have a tag")
)
var (
NoChangelogFileAddedError = eris.Errorf("A changelog file must be added. For more information, check out https://github.com/solo-io/go-utils/tree/master/changelogutils.")
TooManyChangelogFilesAddedError = func(filesAdded int) error {
return eris.Errorf("Only one changelog file can be added in a PR, found %d.", filesAdded)
}
UnexpectedFileInChangelogDirectoryError = func(name string) error {
return eris.Errorf("Found unexpected file %s in changelog directory.", name)
}
InvalidChangelogSubdirectoryNameError = func(name string) error {
return eris.Errorf("%s is not a valid changelog directory name, must be a semver version.", name)
}
ListReleasesError = func(err error) error {
return errors.Wrapf(err, "Error listing releases")
}
MultipleNewVersionsFoundError = func(latest, version1, version2 string) error {
return eris.Errorf("Only one version greater than the latest release %s valid, found %s and %s.", latest, version1, version2)
}
NoNewVersionsFoundError = func(latest string) error {
return eris.Errorf("No new versions greater than the latest release %s found.", latest)
}
AddedChangelogInOldVersionError = func(latest string) error {
return eris.Errorf("Can only add changelog to unreleased version (currently %s)", latest)
}
InvalidUseOfStableApiError = func(tag string) error {
return eris.Errorf("Changelog indicates this is a stable API release, which should be used only to indicate the release of v1.0.0, not %s", tag)
}
UnexpectedProposedVersionError = func(expected, actual string) error {
return eris.Errorf("Expected version %s to be next changelog version, found %s", expected, actual)
}
UnableToGetSettingsError = func(err error) error {
return errors.Wrapf(err, "Unable to read settings file")
}
InvalidLabelError = func(label string, allowed []string) error {
return eris.Errorf("Changelog version has label %s, which isn't in the list of allowed labels: %v", label, allowed)
}
ExpectedVersionLabelError = func(actual string) error {
return eris.Errorf("Expected version %s to to have a semver label suffix", actual)
}
)
Functions ¶
func ChangelogDirExists ¶
func ChangelogDirExists(fs afero.Fs, changelogParentPath string) (bool, error)
Deprecated
func GenerateChangelogForTags ¶ added in v0.10.16
func GenerateChangelogForTags(ctx context.Context, tags []string, reader ChangelogReader, w io.Writer) error
func GenerateChangelogFromLocalDirectory ¶ added in v0.10.16
func GenerateChangelogFromLocalDirectory(ctx context.Context, repoRootPath, owner, repo, changelogDirPath string, w io.Writer) error
func GenerateChangelogMarkdown ¶
func GenerateChangelogMarkdown(changelog *Changelog) string
func GetChangelogFilesAdded ¶ added in v0.11.8
func GetChangelogFilesAdded(ctx context.Context, client githubutils.RepoClient, base, sha string) ([]github.CommitFile, error)
func GetChangelogMarkdownForPR ¶
func GetChangelogMarkdownForPR(owner, repo string) (string, error)
func GetLatestTag ¶
func GetLatestTag(ctx context.Context, owner, repo string) (string, error)
Should return the last released version Deprecated: use githubutils.RepoClient.FindLatestReleaseIncludingPrerelease instead
func GetProposedTag ¶
func GetProposedTag(fs afero.Fs, latestTag, changelogParentPath string) (string, error)
Should return the next version to release, based on the names of the subdirectories in the changelog Will return an error if there is no version, or multiple versions, larger than the latest tag, according to semver Deprecated: use ChangelogValidator instead
func GetProposedTagForRepo
deprecated
func GetProposedTagForRepo(ctx context.Context, client *github.Client, owner, repo string) (string, error)
Deprecated: use ChangelogValidator instead
func GetValidationSettingsPath ¶ added in v0.11.3
func GetValidationSettingsPath() string
func IsInvalidDirectoryNameError ¶
func IsInvalidDirectoryNameError(err error) bool
func IsKnownChangelogFile ¶ added in v0.11.4
func IsKnownChangelogFile(path string) bool
func IsMultipleVersionsFoundError ¶
func IsMultipleVersionsFoundError(err error) bool
func IsNoVersionFoundError ¶
func IsNoVersionFoundError(err error) bool
func RefHasChangelog
deprecated
func RefHasChangelog(ctx context.Context, client *github.Client, owner, repo, sha string) (bool, error)
Deprecated: use githubutils.RepoClient.DirectoryExists
Types ¶
type Changelog ¶
type Changelog struct {
Files []*ChangelogFile
Summary string
Version *versionutils.Version
Closing string
}
func ComputeChangelogForNonRelease
deprecated
func ComputeChangelogForNonRelease(fs afero.Fs, latestTag, proposedTag, changelogParentPath string) (*Changelog, error)
Deprecated: use changelogutils.ChangelogReader instead
func ComputeChangelogForTag
deprecated
func ComputeChangelogForTag(fs afero.Fs, tag, changelogParentPath string) (*Changelog, error)
Deprecated: use changelogutils.ChangelogReader instead
type ChangelogEntry ¶
type ChangelogEntry struct {
Type ChangelogEntryType `json:"type"`
Description string `json:"description"`
IssueLink string `json:"issueLink"`
DependencyOwner string `json:"dependencyOwner,omitempty"`
DependencyRepo string `json:"dependencyRepo,omitempty"`
DependencyTag string `json:"dependencyTag,omitempty"`
ResolvesIssue *bool `json:"resolvesIssue,omitempty"`
}
func (*ChangelogEntry) GetResolvesIssue ¶
func (c *ChangelogEntry) GetResolvesIssue() bool
type ChangelogEntryType ¶
type ChangelogEntryType int
const (
BREAKING_CHANGE ChangelogEntryType = iota
FIX
NEW_FEATURE
NON_USER_FACING
DEPENDENCY_BUMP
HELM
UPGRADE
)
func (ChangelogEntryType) BreakingChange ¶
func (clt ChangelogEntryType) BreakingChange() bool
func (ChangelogEntryType) MarshalJSON ¶
func (clt ChangelogEntryType) MarshalJSON() ([]byte, error)
func (ChangelogEntryType) NewFeature ¶ added in v0.10.28
func (clt ChangelogEntryType) NewFeature() bool
func (*ChangelogEntryType) UnmarshalJSON ¶
func (clt *ChangelogEntryType) UnmarshalJSON(data []byte) error
type ChangelogFile ¶
type ChangelogFile struct {
Entries []*ChangelogEntry `json:"changelog,omitempty"`
ReleaseStableApi *bool `json:"releaseStableApi,omitempty"`
}
func ReadChangelogFile
deprecated
func ReadChangelogFile(fs afero.Fs, path string) (*ChangelogFile, error)
Deprecated: use changelogutils.ChangelogReader instead
func (*ChangelogFile) GetReleaseStableApi ¶
func (c *ChangelogFile) GetReleaseStableApi() bool
func (*ChangelogFile) HasBreakingChange ¶
func (c *ChangelogFile) HasBreakingChange() bool
type ChangelogList ¶ added in v0.10.16
type ChangelogList []*Changelog
type ChangelogReader ¶
type ChangelogReader interface {
GetChangelogForTag(ctx context.Context, tag string) (*Changelog, error)
ReadChangelogFile(ctx context.Context, path string) (*ChangelogFile, error)
}
func NewChangelogReader ¶
func NewChangelogReader(code vfsutils.MountedRepo) ChangelogReader
type ChangelogTmplData ¶ added in v0.10.16
type ChangelogTmplData struct {
ReleaseVersionString string
Summary string
}
type ChangelogValidator ¶
type ChangelogValidator interface {
ShouldCheckChangelog(ctx context.Context) (bool, error)
ValidateChangelog(ctx context.Context) (*ChangelogFile, error)
}
func NewChangelogValidator ¶
func NewChangelogValidator(client githubutils.RepoClient, code vfsutils.MountedRepo, base string) ChangelogValidator
func NewChangelogValidatorWithLabelOrder ¶ added in v0.21.2
func NewChangelogValidatorWithLabelOrder(client githubutils.RepoClient, code vfsutils.MountedRepo, base string, labelOrder []string) ChangelogValidator
type TagComparator ¶ added in v0.21.2
type TagComparator func(greaterThanTag, lessThanTag string) (bool, bool, error)
type ValidationSettings ¶ added in v0.11.1
type ValidationSettings struct {
// If true, then the validator will skip checks to enforce how version numbers are incremented, allowing for more flexible
// versioning for new features or breaking changes
RelaxSemverValidation bool `json:"relaxSemverValidation"`
// If true, then the validator will require a changelog version with a label.
// This is useful to enforce version schemes like we use for envoy-gloo / envoy-gloo-ee, which always have the form:
// $ENVOY_VERSION-$PATCH_NUM
RequireLabel bool `json:"requireLabel"`
// If non-empty, then the validator will reject a changelog if the version's label is not contained in this slice
AllowedLabels []string `json:"allowedLabels"`
// defaults to "". When set, allows for a nested processing schema. ex: "v1.10" would mean only files in "changelog/v1.10" would be processed
ActiveSubdirectory string `json:"activeSubdirectory"`
}
func GetValidationSettings ¶ added in v0.11.8
func GetValidationSettings(ctx context.Context, code vfsutils.MountedRepo, client githubutils.RepoClient) (*ValidationSettings, error)