Skip to content

Commit

Permalink
Merge pull request #2 from DirectXMan12/features/actions-verifier
Browse files Browse the repository at this point in the history
✨ Release Notes Tooling & PR Verifier
  • Loading branch information
k8s-ci-robot committed Oct 1, 2020
2 parents 8f2248c + 3f8b037 commit aec1539
Show file tree
Hide file tree
Showing 22 changed files with 2,924 additions and 0 deletions.
16 changes: 16 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
on:
pull_request_target:
types: [opened, edited, reopened]

jobs:
verify:
runs-on: ubuntu-latest
name: verify PR contents
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Verifier action
id: verifier
uses: ./
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*~
*.swp
*.swo
15 changes: 15 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM golang:1.15 as build

WORKDIR /go/src/verify
COPY verify verify
COPY notes notes
WORKDIR /go/src/verify/verify

ENV CGO_ENABLED=0
RUN go build -o /go/bin/verifypr ./cmd/

FROM gcr.io/distroless/static-debian10

COPY --from=build /go/bin/verifypr /verifypr

ENTRYPOINT ["/verifypr"]
1 change: 1 addition & 0 deletions OWNERS_ALIASES
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ aliases:
# non-admin folks who can approve any PRs in the repo
kubebuilder-approvers:
- camilamacedo86
- vincepri

# folks who can review and LGTM any PRs in the repo (doesn't include
# approvers & admins -- those count too via the OWNERS file)
Expand Down
9 changes: 9 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: 'Verify KubeBuilder PRs'
description: 'Verify PRs for the KubeBuilder project repos & similar'
inputs:
github_token:
description: "the github_token provided by the actions runner"
required: true
runs:
using: docker
image: 'Dockerfile'
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
}
132 changes: 132 additions & 0 deletions notes/common/prefix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
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 (
"fmt"
"strings"
)

type PRType int
func (t PRType) Emoji() string {
switch t {
case UncategorizedPR:
return "<uncategorized>"
case BreakingPR:
return emojiBreaking
case FeaturePR:
return emojiFeature
case BugfixPR:
return emojiBugfix
case DocsPR:
return emojiDocs
case InfraPR:
return emojiInfra
default:
panic(fmt.Sprintf("unrecognized PR type %v", t))
}
}
func (t PRType) String() string {
switch t {
case UncategorizedPR:
return "uncategorized"
case BreakingPR:
return "breaking"
case FeaturePR:
return "feature"
case BugfixPR:
return "bugfix"
case DocsPR:
return "docs"
case InfraPR:
return "infra"
default:
panic(fmt.Sprintf("unrecognized PR type %v", t))
}
}

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 aec1539

Please sign in to comment.