Skip to content

Commit

Permalink
Release note composer
Browse files Browse the repository at this point in the history
This adds in a release notes composer that's based on
controller-runtime's `hack/release/` tooling & the subsequent
cluster-api release tooling written by vincepri, whence it draws pretty
heavily.
  • Loading branch information
DirectXMan12 committed Oct 1, 2020
1 parent 4125276 commit 39377a5
Show file tree
Hide file tree
Showing 12 changed files with 2,350 additions and 0 deletions.
85 changes: 85 additions & 0 deletions notes/common/common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
Copyright 2020 The Kubernetes Authors.
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 common_test

import (
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"

. "sigs.k8s.io/kubebuilder-release-tools/notes/common"
)

func TestCommon(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Common Release Not Parsing Suite")
}

var _ = Describe("PR title parsing", func() {
DescribeTable("prefix to type",
func(title string, expectedType PRType, expectedTitle string) {
prType, finalTitle := PRTypeFromTitle(title)
Expect(prType).To(Equal(expectedType))
Expect(finalTitle).To(Equal(expectedTitle))
},
Entry("should match breaking from ⚠", "⚠ Change leaderlock from ConfigMap to ConfigMapsLeasesResourceLock", BreakingPR, "Change leaderlock from ConfigMap to ConfigMapsLeasesResourceLock"),
Entry("should match breaking from :warning:", ":warning: admission responses with raw Status", BreakingPR, "admission responses with raw Status"),
Entry("should match feature from ✨", "✨CreateOrPatch", FeaturePR, "CreateOrPatch"),
Entry("should match feature from :sparkles:", ":sparkles: Add error check for multiple apiTypes as reconciliation object", FeaturePR, "Add error check for multiple apiTypes as reconciliation object"),
Entry("should match bugfix from 🐛", "🐛 Controller.Watch() should not store watches if already started", BugfixPR, "Controller.Watch() should not store watches if already started"),
Entry("should match bugfix from :bug:", ":bug: Ensure that webhook server is thread/start-safe", BugfixPR, "Ensure that webhook server is thread/start-safe"),
Entry("should match docs from 📖", "📖 Nit: improve doc string", DocsPR, "Nit: improve doc string"),
Entry("should match docs from :book:", ":book: Fix typo", DocsPR, "Fix typo"),
Entry("should match infra from 🌱", "🌱 some infra stuff (couldn't find in log)", InfraPR, "some infra stuff (couldn't find in log)"),
Entry("should match infra from :seedling:", ":seedling: Update Go mod version to 1.15", InfraPR, "Update Go mod version to 1.15"),
Entry("should match infra from 🏃(deprecated)", "🏃 hack/setup-envtest.sh: follow-up from #1092", InfraPR, "hack/setup-envtest.sh: follow-up from #1092"),
Entry("should match infra from :running: (deprecated)", ":running: Proposal to extract cluster-specifics out of the Manager", InfraPR, "Proposal to extract cluster-specifics out of the Manager"),
Entry("should put anything else as uncategorized", "blah blah", UncategorizedPR, "blah blah"),
)

It("should strip space from the start and end of the final title", func() {
prType, title := PRTypeFromTitle(":sparkles: this is a feature")
Expect(title).To(Equal("this is a feature"))
Expect(prType).To(Equal(FeaturePR))
})

It("should strip space before considering the prefix", func() {
prType, title := PRTypeFromTitle(" :sparkles:this is a feature")
Expect(title).To(Equal("this is a feature"))
Expect(prType).To(Equal(FeaturePR))
})

It("should strip variation selectors from the start of the final title", func() {
prType, title := PRTypeFromTitle("✨\uFE0FTruly sparkly")
Expect(prType).To(Equal(FeaturePR))
Expect(title).To(Equal("Truly sparkly"))
})

It("should ingore emoji in the middle of the message", func() {
prType, title := PRTypeFromTitle("this is not a ✨ feature")
Expect(title).To(Equal("this is not a ✨ feature"))
Expect(prType).To(Equal(UncategorizedPR))
})

It("should ignore github text->emoji in the middle of the message", func() {
prType, title := PRTypeFromTitle("this is not a :sparkles: feature")
Expect(title).To(Equal("this is not a :sparkles: feature"))
Expect(prType).To(Equal(UncategorizedPR))
})
})
48 changes: 48 additions & 0 deletions notes/common/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
Copyright 2020 The Kubernetes Authors.
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 common

import (
"errors"
"fmt"
"os/exec"
)

// ErrOut wraps exec.ExitErrors so that the message displays their
// stderr output. If the error is not an exist error, or does not
// wrap one, this returns the error without any changes.
func ErrOut(err error) error {
var exitErr *exec.ExitError
if !errors.As(err, &exitErr) {
return err
}
return errOut{actual: exitErr}
}

// errOut is an Error that prints the underlying ExitError's stderr in addition
// to the normal message.
type errOut struct {
actual *exec.ExitError
}

func (e errOut) Error() string {
return fmt.Sprintf("[%v] %q", e.actual.Error(), string(e.actual.Stderr))
}

func (e errOut) Unwrap() error {
return e.actual
}
95 changes: 95 additions & 0 deletions notes/common/prefix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
Copyright 2020 The Kubernetes Authors.
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 common

import (
"strings"
)

type PRType int

const (
UncategorizedPR PRType = iota
BreakingPR
FeaturePR
BugfixPR
DocsPR
InfraPR
)

// NB(directxman12): These are constants because some folks' dev environments like
// to inject extra combining characters into the mix (generally variation selector 16,
// which indicates emoji presentation), so we want to check that these are *just* the
// character without the combining parts. Note that they're a rune, so that they
// can *only* be one codepoint.
const (
emojiFeature = string('✨')
emojiBugfix = string('🐛')
emojiDocs = string('📖')
emojiInfra = string('🌱')
emojiBreaking = string('⚠')
emojiInfraLegacy = string('🏃')
)

func PRTypeFromTitle(title string) (PRType, string) {
title = strings.TrimSpace(title)

if len(title) == 0 {
return UncategorizedPR, title
}

var prType PRType
switch {
case strings.HasPrefix(title, ":sparkles:"), strings.HasPrefix(title, emojiFeature):
title = strings.TrimPrefix(title, ":sparkles:")
title = strings.TrimPrefix(title, emojiFeature)
prType = FeaturePR
case strings.HasPrefix(title, ":bug:"), strings.HasPrefix(title, emojiBugfix):
title = strings.TrimPrefix(title, ":bug:")
title = strings.TrimPrefix(title, emojiBugfix)
prType = BugfixPR
case strings.HasPrefix(title, ":book:"), strings.HasPrefix(title, emojiDocs):
title = strings.TrimPrefix(title, ":book:")
title = strings.TrimPrefix(title, emojiDocs)
prType = DocsPR
case strings.HasPrefix(title, ":seedling:"), strings.HasPrefix(title, emojiInfra):
title = strings.TrimPrefix(title, ":seedling:")
title = strings.TrimPrefix(title, emojiInfra)
prType = InfraPR
case strings.HasPrefix(title, ":warning:"), strings.HasPrefix(title, emojiBreaking):
title = strings.TrimPrefix(title, ":warning:")
title = strings.TrimPrefix(title, emojiBreaking)
prType = BreakingPR
case strings.HasPrefix(title, ":running:"), strings.HasPrefix(title, emojiInfraLegacy):
// This has been deprecated in favor of :seedling:
title = strings.TrimPrefix(title, ":running:")
title = strings.TrimPrefix(title, emojiInfraLegacy)
prType = InfraPR
default:
return UncategorizedPR, title
}

// strip the variation selector from the title, if present
// (some systems sneak it in -- my guess is OSX)
title = strings.TrimPrefix(title, "\uFE0F")

// NB(directxman12): there are a few other cases like the variation selector,
// but I can't seem to dig them up. If something doesn't parse as expected,
// check for zero-width characters and add handling here.

return prType, strings.TrimSpace(title)
}
Loading

0 comments on commit 39377a5

Please sign in to comment.