Skip to content

Commit

Permalink
feat(repository): patch now patches all fields
Browse files Browse the repository at this point in the history
  • Loading branch information
tstollin committed Dec 9, 2024
1 parent 844e03e commit 97f3e2d
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 55 deletions.
4 changes: 1 addition & 3 deletions api/generated_model_repository_configuration_dto.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 9 additions & 5 deletions api/generated_model_repository_configuration_patch_dto.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 16 additions & 5 deletions api/openapi-v3-spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2085,9 +2085,6 @@ components:
archived:
description: Moves the repository into the archive.
type: boolean
unmanaged:
description: 'Repository will not be configured, also not archived.'
type: boolean
refProtections:
$ref: '#/components/schemas/RefProtections'
requireIssue:
Expand Down Expand Up @@ -2127,6 +2124,10 @@ components:
type: array
items:
$ref: '#/components/schemas/MergeStrategy'
defaultTasks:
type: array
items:
$ref: '#/components/schemas/RepositoryConfigurationDefaultTaskDto'
branchNameRegex:
description: Use an explicit branch name regex.
type: string
Expand Down Expand Up @@ -2176,9 +2177,19 @@ components:
archived:
description: Moves the repository into the archive.
type: boolean
unmanaged:
description: 'Repository will not be configured, also not archived.'
refProtections:
$ref: '#/components/schemas/RefProtections'
requireIssue:
description: 'Configures JQL matcher with query: issuetype in (Story, Bug) AND ''Risk Level'' is not EMPTY'
type: boolean
requireConditions:
description: Configuration of conditional builds as map of structs (key name e.g. some-key) of target references.
type: object
examples:
- some-key:
refMatcher: main
additionalProperties:
$ref: '#/components/schemas/ConditionReferenceDto'
actionsAccess:
description: Control how the repository is used by GitHub Actions workflows in other repositories
type: string
Expand Down
84 changes: 78 additions & 6 deletions internal/service/repositories/repositories.go
Original file line number Diff line number Diff line change
Expand Up @@ -447,27 +447,99 @@ func patchConfiguration(patch *openapi.RepositoryConfigurationPatchDto, original
}
return &openapi.RepositoryConfigurationDto{
AccessKeys: patchAccessKeys(patch.AccessKeys, original.AccessKeys),
MergeConfig: patchMergeConfig(patch.MergeConfig, original.MergeConfig),
DefaultTasks: patchSlice(patch.DefaultTasks, original.DefaultTasks),
BranchNameRegex: patchStringPtr(patch.BranchNameRegex, original.BranchNameRegex),
CommitMessageRegex: patchStringPtr(patch.CommitMessageRegex, original.CommitMessageRegex),
CommitMessageType: patchStringPtr(patch.CommitMessageType, original.CommitMessageType),
RequireSuccessfulBuilds: patchPtr[int32](patch.RequireSuccessfulBuilds, original.RequireSuccessfulBuilds),
RequireApprovals: patchPtr[int32](patch.RequireApprovals, original.RequireApprovals),
ExcludeMergeCommits: patchPtr[bool](patch.ExcludeMergeCommits, original.ExcludeMergeCommits),
ExcludeMergeCheckUsers: patchExcludeMergeCheckUsers(patch.ExcludeMergeCheckUsers, original.ExcludeMergeCheckUsers),
Webhooks: patchWebhooks(patch.Webhooks, original.Webhooks),
Approvers: patchApprovers(patch.Approvers, original.Approvers),
Watchers: patchStringSlice(patch.Watchers, original.Watchers),
Watchers: patchSlice(patch.Watchers, original.Watchers),
Archived: patchPtr[bool](patch.Archived, original.Archived),
RefProtections: patchRefProtections(patch.RefProtections, original.RefProtections),
RequireIssue: patchPtr[bool](patch.RequireIssue, original.RequireIssue),
RequireConditions: patchRequireConditions(patch.RequireConditions, original.RequireConditions),
ActionsAccess: patchStringPtr(patch.ActionsAccess, original.ActionsAccess),
// fields not allowed for patching carry over from original
RequireIssue: original.RequireIssue,
RequireConditions: original.RequireConditions,
RefProtections: original.RefProtections,
}
} else {
return original
}
}

func patchMergeConfig(patch *openapi.RepositoryConfigurationDtoMergeConfig, original *openapi.RepositoryConfigurationDtoMergeConfig) *openapi.RepositoryConfigurationDtoMergeConfig {
if patch != nil {
if original == nil {
return patch
} else {
return &openapi.RepositoryConfigurationDtoMergeConfig{
DefaultStrategy: patchPtr(patch.DefaultStrategy, original.DefaultStrategy),
Strategies: patchSlice(patch.Strategies, original.Strategies),
}
}
} else {
return original
}
}

func patchRefProtections(patch *openapi.RefProtections, original *openapi.RefProtections) *openapi.RefProtections {
if patch != nil {
if original == nil {
return patch
} else {
return &openapi.RefProtections{
Branches: patchRefProtectionsBranches(patch.Branches, original.Branches),
Tags: patchRefProtectionsTags(patch.Tags, original.Tags),
}
}
} else {
return original
}
}

func patchRefProtectionsBranches(patch *openapi.RefProtectionsBranches, original *openapi.RefProtectionsBranches) *openapi.RefProtectionsBranches {
if patch != nil {
return &openapi.RefProtectionsBranches{
RequirePR: patchSlice(patch.RequirePR, original.RequirePR),
PreventAllChanges: patchSlice(patch.PreventAllChanges, original.PreventAllChanges),
PreventCreation: patchSlice(patch.PreventCreation, original.PreventCreation),
PreventDeletion: patchSlice(patch.PreventDeletion, original.PreventDeletion),
PreventPush: patchSlice(patch.PreventPush, original.PreventPush),
PreventForcePush: patchSlice(patch.PreventForcePush, original.PreventForcePush),
}
} else {
return original
}
}

func patchRefProtectionsTags(patch *openapi.RefProtectionsTags, original *openapi.RefProtectionsTags) *openapi.RefProtectionsTags {
if patch != nil {
return &openapi.RefProtectionsTags{
PreventAllChanges: patchSlice(patch.PreventAllChanges, original.PreventAllChanges),
PreventCreation: patchSlice(patch.PreventCreation, original.PreventCreation),
PreventDeletion: patchSlice(patch.PreventDeletion, original.PreventDeletion),
PreventForcePush: patchSlice(patch.PreventForcePush, original.PreventForcePush),
}
} else {
return original
}
}

func patchRequireConditions(patch map[string]openapi.ConditionReferenceDto, original map[string]openapi.ConditionReferenceDto) map[string]openapi.ConditionReferenceDto {
if patch != nil {
if len(patch) == 0 {
// remove
return nil
}
return patch
} else {
return original
}
}

func patchApprovers(patch map[string][]string, original map[string][]string) map[string][]string {
return patchMapStringListString(patch, original)
}
Expand Down Expand Up @@ -549,7 +621,7 @@ func patchAccessKeys(patch []openapi.RepositoryConfigurationAccessKeyDto, origin
}
}

func patchStringSlice(patch []string, original []string) []string {
func patchSlice[E any](patch []E, original []E) []E {
if patch != nil {
if len(patch) == 0 {
// remove
Expand Down
34 changes: 0 additions & 34 deletions test/acceptance/repositoryctl_acc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -683,40 +683,6 @@ func TestPATCHRepository_Success(t *testing.T) {
hasSentNotification(t, "receivesRepository", "karma-wrapper.helm-chart", types.ModifiedEvent, types.RepositoryPayload, &payload)
}

func TestPATCHRepository_UnsupportedConfigurationFieldsAreIgnored(t *testing.T) {
tstReset()

docs.Given("Given an authenticated admin user")
token := tstValidAdminToken()

docs.When("When they perform a patch changing unsupported fields in the configuration of an existing repository")
body := tstRepositoryPatchWithIgnoredConfigurationFields()
response, err := tstPerformPatch("/rest/api/v1/repositories/karma-wrapper.helm-chart", token, &body)

docs.Then("Then the request is successful and the response is as expected")
tstAssert(t, response, err, http.StatusOK, "repository-patch.json")

docs.Then("And the repository has been correctly written, committed and pushed")
filename := "owners/some-owner/repositories/karma-wrapper.helm-chart.yaml"
require.Equal(t, tstRepositoryExpectedYamlKarmaWrapper(), metadataImpl.ReadContents(filename))
require.True(t, metadataImpl.FilesCommitted[filename])
require.True(t, metadataImpl.Pushed)

docs.Then("And the repository has been cached and can be read again")
readAgain, err := tstPerformGet("/rest/api/v1/repositories/karma-wrapper.helm-chart", tstUnauthenticated())
tstAssert(t, readAgain, err, http.StatusOK, "repository-patch.json")

docs.Then("And a kafka message notifying other instances of the update has been sent")
require.Equal(t, 1, len(kafkaImpl.Recording))
actual, _ := json.Marshal(kafkaImpl.Recording[0])
require.Equal(t, tstRepositoryExpectedKafka("karma-wrapper.helm-chart"), string(actual))

docs.Then("And a notification has been sent to all matching owners")
payload := tstUpdatedRepositoryPayload()
hasSentNotification(t, "receivesModified", "karma-wrapper.helm-chart", types.ModifiedEvent, types.RepositoryPayload, &payload)
hasSentNotification(t, "receivesRepository", "karma-wrapper.helm-chart", types.ModifiedEvent, types.RepositoryPayload, &payload)
}

func TestPATCHRepository_NoChangeSuccess(t *testing.T) {
tstReset()

Expand Down
27 changes: 26 additions & 1 deletion test/acceptance/util_dtos_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package acceptance
import (
"github.com/Interhyp/metadata-service/api"
"github.com/Interhyp/metadata-service/internal/repository/notifier"
"github.com/Interhyp/metadata-service/internal/util"
)

func ptr[T interface{}](v T) *T {
Expand Down Expand Up @@ -281,7 +282,18 @@ func tstRepositoryPatch() openapi.RepositoryPatchDto {
return openapi.RepositoryPatchDto{
Mainline: ptr("main"),
Configuration: &openapi.RepositoryConfigurationPatchDto{
BranchNameRegex: ptr("testing_.*"),
BranchNameRegex: ptr("testing_.*"),
RequireIssue: ptr(true),
RequireConditions: make(map[string]openapi.ConditionReferenceDto),
RefProtections: &openapi.RefProtections{
Branches: &openapi.RefProtectionsBranches{
RequirePR: []openapi.ProtectedRef{
{
Pattern: ".*",
},
},
},
},
},
TimeStamp: "2022-11-06T18:14:10Z",
CommitHash: "6c8ac2c35791edf9979623c717a243fc53400000",
Expand Down Expand Up @@ -358,6 +370,11 @@ mainline: main
unittest: false
configuration:
branchNameRegex: testing_.*
refProtections:
branches:
requirePR:
- pattern: .*
requireIssue: true
`
}

Expand Down Expand Up @@ -394,6 +411,14 @@ func tstUpdatedRepositoryPayload() openapi.NotificationPayload {
repo.CommitHash = "6c8ac2c35791edf9979623c717a2430000000000"
repo.Configuration = &openapi.RepositoryConfigurationDto{
BranchNameRegex: ptr("testing_.*"),
RequireIssue: util.Ptr(true),
RefProtections: &openapi.RefProtections{
Branches: &openapi.RefProtectionsBranches{
RequirePR: []openapi.ProtectedRef{
{Pattern: ".*"},
},
},
},
}
return notifier.AsPayload(repo)
}
12 changes: 11 additions & 1 deletion test/resources/acceptance-expected/repository-patch.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
{
"commitHash": "6c8ac2c35791edf9979623c717a2430000000000",
"configuration": {
"branchNameRegex": "testing_.*"
"branchNameRegex": "testing_.*",
"refProtections": {
"branches": {
"requirePR": [
{
"pattern": ".*"
}
]
}
},
"requireIssue": true
},
"jiraIssue": "ISSUE-2345",
"mainline": "main",
Expand Down

0 comments on commit 97f3e2d

Please sign in to comment.