Skip to content
This repository was archived by the owner on Jul 15, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion internal/acorn/config/customconfigint.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,20 @@ type CustomConfiguration interface {

CheckWarnMissingMainlineProtection() bool
CheckExpectedRequiredConditions() []CheckedRequiredConditions
CheckedExpectedExemptions() []CheckedExpectedExemption
}

type CheckedRequiredConditions struct {
Name string `yaml:"name" json:"name"`
AnnotationLevel string `yaml:"annotationLevel" json:"annotationLevel"`
RefMatcher string `yaml:"refMatcher" json:"refMatcher"`
}

type CheckedExpectedExemption struct {
Name string `yaml:"name" json:"name"`
RefMatcher string `yaml:"refMatcher" json:"refMatcher"`
Exemptions []string `yaml:"exemptions" json:"exemptions"`
}

type NotificationConsumerConfig struct {
Subscribed map[types.NotificationPayloadType]map[types.NotificationEventType]struct{}
ConsumerURL string
Expand Down Expand Up @@ -131,4 +137,5 @@ const (
KeyFormattingActionCommitMsgPrefix = "FORMATTING_ACTION_COMMIT_MSG_PREFIX"
KeyCheckWarnMissingMainlineProtection = "CHECK_WARN_MISSING_MAINLINE_PROTECTION"
KeyCheckExpectedRequiredConditions = "CHECK_EXPECTED_REQUIRED_CONDITIONS"
KeyCheckExpectedExemptions = "CHECK_EXPECTED_EXEMPTIONS"
)
4 changes: 4 additions & 0 deletions internal/repository/config/accessors.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,7 @@ func (c *CustomConfigImpl) CheckWarnMissingMainlineProtection() bool {
func (c *CustomConfigImpl) CheckExpectedRequiredConditions() []config.CheckedRequiredConditions {
return c.VCheckExpectedRequiredConditions
}

func (c *CustomConfigImpl) CheckedExpectedExemptions() []config.CheckedExpectedExemption {
return c.VCheckExpectedExemptions
}
11 changes: 11 additions & 0 deletions internal/repository/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,17 @@ var CustomConfigItems = []auconfigapi.ConfigItem{
return err
},
},
{
Key: config.KeyCheckExpectedExemptions,
EnvName: config.KeyCheckExpectedExemptions,
Description: "A JSON list defining all expected exemptions which will be checked for by the GitHub check for all defined repository.yaml files. Each entry contains the 'name' of the exemption, the expected 'refMatcher' and the 'exemptions'.",
Default: "[]",
Validate: func(key string) error {
value := auconfigenv.Get(key)
_, err := parseCheckExpectedExemptions(value)
return err
},
},
}

func ObtainPositiveInt64Validator() func(key string) error {
Expand Down
13 changes: 13 additions & 0 deletions internal/repository/config/plumbing.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ type CustomConfigImpl struct {
VFormattingActionCommitMsgPrefix string
VCheckWarnMissingMainlineProtection bool
VCheckExpectedRequiredConditions []config.CheckedRequiredConditions
VCheckExpectedExemptions []config.CheckedExpectedExemption

VKafkaConfig *kafka.Config
GitUrlMatcher *regexp.Regexp
Expand Down Expand Up @@ -123,6 +124,7 @@ func (c *CustomConfigImpl) Obtain(getter func(key string) string) {
c.VYamlIndentation = toInt(getter(config.KeyYamlIndentation))
c.VFormattingActionCommitMsgPrefix = getter(config.KeyFormattingActionCommitMsgPrefix)
c.VCheckExpectedRequiredConditions, _ = parseCheckExpectedRequiredConditions(getter(config.KeyCheckExpectedRequiredConditions))
c.VCheckExpectedExemptions, _ = parseCheckExpectedExemptions(getter(config.KeyCheckExpectedExemptions))
c.VCheckWarnMissingMainlineProtection, _ = strconv.ParseBool(getter(config.KeyCheckWarnMissingMainlineProtection))
}

Expand Down Expand Up @@ -252,3 +254,14 @@ func parseCheckExpectedRequiredConditions(rawJson string) ([]config.CheckedRequi
}
return result, nil
}

func parseCheckExpectedExemptions(rawJson string) ([]config.CheckedExpectedExemption, error) {
var parsed []config.CheckedExpectedExemption
if rawJson == "[]" {
return make([]config.CheckedExpectedExemption, 0), nil
}
if err := json.Unmarshal([]byte(rawJson), &parsed); err != nil {
return make([]config.CheckedExpectedExemption, 0), err
}
return parsed, nil
}
16 changes: 12 additions & 4 deletions internal/service/check/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ func (h *Impl) validateFiles(ctx context.Context, fs billy.Filesystem) (CheckRes
johnnie := MetadataYamlFileWalker(fs,
WithIndentation(h.CustomConfiguration.YamlIndentation()),
WithExpectedRequiredConditions(h.CustomConfiguration.CheckExpectedRequiredConditions()),
WithExpectedExemptions(h.CustomConfiguration.CheckedExpectedExemptions()),
WithMainlinePrProtection(h.CustomConfiguration.CheckWarnMissingMainlineProtection()),
)
err := johnnie.ValidateMetadata()
Expand All @@ -172,6 +173,7 @@ func (h *Impl) validateFiles(ctx context.Context, fs billy.Filesystem) (CheckRes
func walkerToCheckRunOutput(johnnie *MetadataWalker) CheckResult {
result := CheckResult{
conclusion: repository.CheckRunSuccess,
actions: make([]*github.CheckRunAction, 0),
}

title := SuccessValidationTitle
Expand Down Expand Up @@ -201,12 +203,18 @@ func walkerToCheckRunOutput(johnnie *MetadataWalker) CheckResult {
Text: details,
}

if johnnie.hasFormatErrors {
if johnnie.hasFormatErrors || len(johnnie.hasMissingRequiredConditionExemptions) > 0 {
actionLabel := "Fix formatting"
description := "Adds a new commit with fixed formatting."
if len(johnnie.hasMissingRequiredConditionExemptions) > 0 {
actionLabel = "Fix missing exemptions"
description = "Adds a new commit with missing exemptions."
}
result.actions = []*github.CheckRunAction{
{
Label: "Fix formatting",
Description: "Adds a new commit with fixed formatting.",
Identifier: FixFormattingAction,
Label: actionLabel,
Description: description,
Identifier: FixAction,
},
}
}
Expand Down
45 changes: 28 additions & 17 deletions internal/service/check/check_actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import (
)

const (
FixFormattingAction = "fix-formatting"
ActionTimeout = 1 * time.Minute
FixAction = "fix-all"
ActionTimeout = 1 * time.Minute
)

func (h *Impl) PerformRequestedAction(ctx context.Context, requestedAction string, checkRun *github.CheckRun, requestingUser *github.User) error {
Expand All @@ -24,19 +24,33 @@ func (h *Impl) PerformRequestedAction(ctx context.Context, requestedAction strin
defer cancel()

switch requestedAction {
case FixFormattingAction:
return h.commitFormatFixes(independentCtx, checkRun.GetCheckSuite().GetHeadBranch(), requestingUser.GetLogin())
case FixAction:
msg := "formatting files/adding missing exemptions"
fixFunc := func(branchName string, worktree *git.Worktree) error {
aulogging.Logger.Ctx(ctx).Debug().Printf("%s on branch %s", msg, branchName)
err := MetadataYamlFileWalker(worktree.Filesystem,
WithIndentation(h.CustomConfiguration.YamlIndentation()),
).FormatMetadata()
if err != nil {
return err
}
err = MetadataYamlFileWalker(worktree.Filesystem,
WithExpectedExemptions(h.CustomConfiguration.CheckedExpectedExemptions()),
).FixExemptions()
return err
}
return h.commitFixes(independentCtx, fixFunc, checkRun.GetCheckSuite().GetHeadBranch(), requestingUser.GetLogin(), msg)
}

aulogging.Logger.Ctx(independentCtx).Info().Printf("successfully processed webhook for requested_action %s (suite: %d|run: %d)", requestedAction, checkRun.CheckSuite.GetID(), checkRun.GetID())
return nil
}

func (h *Impl) commitFormatFixes(ctx context.Context, branchName string, user string) error {
func (h *Impl) commitFixes(ctx context.Context, fixFunc func(branchName string, worktree *git.Worktree) error, branchName string, user string, msg string) error {
if branchName == "" {
return fmt.Errorf("missing branch name for formatting files")
return fmt.Errorf("missing branch name for fixing %s", msg)
}
aulogging.Logger.Ctx(ctx).Info().Printf("start fixing file format on branch %s", branchName)
aulogging.Logger.Ctx(ctx).Info().Printf("start fixing '%s' on branch %s", msg, branchName)
author, err := h.Github.GetUser(ctx, user)
if err != nil {
return err
Expand All @@ -48,29 +62,26 @@ func (h *Impl) commitFormatFixes(ctx context.Context, branchName string, user st
return err
}

aulogging.Logger.Ctx(ctx).Debug().Printf("formatting files on branch %s", branchName)
err = MetadataYamlFileWalker(worktree.Filesystem,
WithIndentation(h.CustomConfiguration.YamlIndentation()),
).FormatMetadata()
err = fixFunc(branchName, worktree)
if err != nil {
return err
}

aulogging.Logger.Ctx(ctx).Debug().Printf("committing formatted files onto branch %s", branchName)
err = h.commit(worktree, author)
aulogging.Logger.Ctx(ctx).Debug().Printf("committing %s onto branch %s", msg, branchName)
err = h.commit(worktree, author, msg)
if err != nil {
return err
}

aulogging.Logger.Ctx(ctx).Debug().Printf("pushing formatted files onto branch %s", branchName)
aulogging.Logger.Ctx(ctx).Debug().Printf("pushing %s onto branch %s", msg, branchName)
err = repo.PushContext(ctx, &git.PushOptions{
Auth: h.AuthProvider.ProvideAuth(ctx),
RemoteName: "origin",
})
if err != nil {
return err
}
aulogging.Logger.Ctx(ctx).Info().Printf("finished fixing file format on branch %s", branchName)
aulogging.Logger.Ctx(ctx).Info().Printf("finished fixing '%s' on branch %s", msg, branchName)
return nil
}

Expand All @@ -92,9 +103,9 @@ func (h *Impl) clone(ctx context.Context, branchName string) (*git.Repository, *
return repo, worktree, nil
}

func (h *Impl) commit(worktree *git.Worktree, author *github.User) error {
func (h *Impl) commit(worktree *git.Worktree, author *github.User, msg string) error {
commitTimestamp := h.timestamp.Now()
commitMsg := fmt.Sprintf("%sFormat files", h.CustomConfiguration.FormattingActionCommitMsgPrefix())
commitMsg := fmt.Sprintf("%s%s", h.CustomConfiguration.FormattingActionCommitMsgPrefix(), msg)
_, err := worktree.Commit(commitMsg, &git.CommitOptions{
All: true,
Author: &object.Signature{
Expand Down
30 changes: 22 additions & 8 deletions internal/service/check/yamlwalker.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,29 @@ type walkedRepos struct {
keyToPath map[string]string
}
type MetadataWalker struct {
fs billy.Filesystem
Annotations []*github.CheckRunAnnotation
Errors map[string]error
IgnoredWithReason map[string]string
walkedRepos walkedRepos
fmtEngine yamlfmt.Engine
hasFormatErrors bool
config Config
fs billy.Filesystem
Annotations []*github.CheckRunAnnotation
Errors map[string]error
IgnoredWithReason map[string]string
walkedRepos walkedRepos
fmtEngine yamlfmt.Engine
hasFormatErrors bool
hasMissingRequiredConditionExemptions []MissingRequiredConditionExemption
config Config
}

type MissingRequiredConditionExemption struct {
Name string
RefMatcher string
Exemptions []string
}

type Config struct {
rootDir string
indentation int
requireMainlinePrProtection bool
expectedRequiredConditions []config.CheckedRequiredConditions
expectedExemptions []config.CheckedExpectedExemption
}

type Option = func(config *Config)
Expand Down Expand Up @@ -61,6 +69,12 @@ func WithExpectedRequiredConditions(expectedReqConditions []config.CheckedRequir
}
}

func WithExpectedExemptions(expectedExemptions []config.CheckedExpectedExemption) Option {
return func(config *Config) {
config.expectedExemptions = expectedExemptions
}
}

const lineBreakStyle = yamlfmt.LineBreakStyleLF
const lineSeparatorCharacter = "\n"

Expand Down
115 changes: 115 additions & 0 deletions internal/service/check/yamlwalker_exemptions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package check

import (
"fmt"
openapi "github.com/Interhyp/metadata-service/api"
"github.com/Interhyp/metadata-service/internal/acorn/config"
"github.com/go-git/go-billy/v5/util"
"gopkg.in/yaml.v3"
"io/fs"
"strings"
)

func (v *MetadataWalker) FixExemptions() error {
return util.Walk(v.fs, v.config.rootDir, v.fixExemptionsFunc)
}

func (v *MetadataWalker) fixExemptionsFunc(path string, info fs.FileInfo, err error) error {
return v.walkFunc(
func(fileContents []byte) error {
return v.fixExemptionsInFile(fileContents, path)
})(path, info, err)
}

func (v *MetadataWalker) fixExemptionsInFile(fileContents []byte, path string) error {
if strings.Contains(path, "/repositories/") {
v.validateRepositoryFile(path, string(fileContents))
if len(v.hasMissingRequiredConditionExemptions) > 0 {
dto := &openapi.RepositoryDto{}
_ = parseStrict(path, string(fileContents), dto)
for _, missingExemption := range v.hasMissingRequiredConditionExemptions {
switch missingExemption.Name {
case fmt.Sprintf("%s.requirePR", refProtectionBranchIdentifier):
var newRefProtection []openapi.ProtectedRef
for _, refProtection := range dto.Configuration.RefProtections.Branches.RequirePR {
newRefProtection = addMissingExemption(refProtection, missingExemption, newRefProtection)
}
dto.Configuration.RefProtections.Branches.RequirePR = newRefProtection
case fmt.Sprintf("%s.preventAllChanges", refProtectionBranchIdentifier):
var newRefProtection []openapi.ProtectedRef
for _, refProtection := range dto.Configuration.RefProtections.Branches.PreventAllChanges {
newRefProtection = addMissingExemption(refProtection, missingExemption, newRefProtection)
}
dto.Configuration.RefProtections.Branches.PreventAllChanges = newRefProtection
case fmt.Sprintf("%s.preventCreation", refProtectionBranchIdentifier):
var newRefProtection []openapi.ProtectedRef
for _, refProtection := range dto.Configuration.RefProtections.Branches.PreventCreation {
newRefProtection = addMissingExemption(refProtection, missingExemption, newRefProtection)
}
dto.Configuration.RefProtections.Branches.PreventCreation = newRefProtection
case fmt.Sprintf("%s.preventDeletion", refProtectionBranchIdentifier):
var newRefProtection []openapi.ProtectedRef
for _, refProtection := range dto.Configuration.RefProtections.Branches.PreventDeletion {
newRefProtection = addMissingExemption(refProtection, missingExemption, newRefProtection)
}
dto.Configuration.RefProtections.Branches.PreventDeletion = newRefProtection
case fmt.Sprintf("%s.preventPush", refProtectionBranchIdentifier):
var newRefProtection []openapi.ProtectedRef
for _, refProtection := range dto.Configuration.RefProtections.Branches.PreventPush {
newRefProtection = addMissingExemption(refProtection, missingExemption, newRefProtection)
}
dto.Configuration.RefProtections.Branches.PreventPush = newRefProtection
case fmt.Sprintf("%s.preventForcePush", refProtectionBranchIdentifier):
var newRefProtection []openapi.ProtectedRef
for _, refProtection := range dto.Configuration.RefProtections.Branches.PreventForcePush {
newRefProtection = addMissingExemption(refProtection, missingExemption, newRefProtection)
}
dto.Configuration.RefProtections.Branches.PreventForcePush = newRefProtection
case fmt.Sprintf("%s.preventAllChanges", refProtectionTagIdentifier):
var newRefProtection []openapi.ProtectedRef
for _, refProtection := range dto.Configuration.RefProtections.Tags.PreventAllChanges {
newRefProtection = addMissingExemption(refProtection, missingExemption, newRefProtection)
}
dto.Configuration.RefProtections.Tags.PreventAllChanges = newRefProtection
case fmt.Sprintf("%s.preventCreation", refProtectionTagIdentifier):
var newRefProtection []openapi.ProtectedRef
for _, refProtection := range dto.Configuration.RefProtections.Tags.PreventCreation {
newRefProtection = addMissingExemption(refProtection, missingExemption, newRefProtection)
}
dto.Configuration.RefProtections.Tags.PreventCreation = newRefProtection
case fmt.Sprintf("%s.preventDeletion", refProtectionTagIdentifier):
var newRefProtection []openapi.ProtectedRef
for _, refProtection := range dto.Configuration.RefProtections.Tags.PreventDeletion {
newRefProtection = addMissingExemption(refProtection, missingExemption, newRefProtection)
}
dto.Configuration.RefProtections.Tags.PreventDeletion = newRefProtection
case fmt.Sprintf("%s.preventForcePush", refProtectionTagIdentifier):
var newRefProtection []openapi.ProtectedRef
for _, refProtection := range dto.Configuration.RefProtections.Tags.PreventForcePush {
newRefProtection = addMissingExemption(refProtection, missingExemption, newRefProtection)
}
dto.Configuration.RefProtections.Tags.PreventForcePush = newRefProtection
}
if isExpectedExemptionCondition(config.CheckedExpectedExemption{Name: missingExemption.Name}) {
if current, ok := dto.Configuration.RequireConditions[missingExemption.Name]; ok && current.RefMatcher == missingExemption.RefMatcher {
current.Exemptions = append(current.Exemptions, missingExemption.Exemptions...)
dto.Configuration.RequireConditions[missingExemption.Name] = current
}
}
}
fixed, err := yaml.Marshal(dto)
if err != nil {
return err
}
return v.formatSingleYamlFile(fixed, path)
}
}
return nil
}

func addMissingExemption(refProtection openapi.ProtectedRef, missingExemption MissingRequiredConditionExemption, newRefProtection []openapi.ProtectedRef) []openapi.ProtectedRef {
if refProtection.Pattern == missingExemption.RefMatcher {
refProtection.Exemptions = append(refProtection.Exemptions, missingExemption.Exemptions...)
}
return append(newRefProtection, refProtection)
}
Loading