changelogutils

package
v0.25.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 16, 2024 License: Apache-2.0 Imports: 17 Imported by: 4

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

View Source
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"
)
View Source
const (
	MasterBranch           = "master"
	ValidationSettingsFile = "validation.yaml"
)

Variables

View Source
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")
	}
)
View Source
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")
)
View Source
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) String

func (clt ChangelogEntryType) String() string

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

func (ChangelogList) Len added in v0.10.16

func (l ChangelogList) Len() int

func (ChangelogList) Less added in v0.10.16

func (l ChangelogList) Less(i, j int) bool

it is a bug to pass a changelog list containing a nil version to this function

func (ChangelogList) Swap added in v0.10.16

func (l ChangelogList) Swap(i, j int)

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)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL