Skip to content

Commit 8d68840

Browse files
authored
Support multiple levels of nesting for promoted "components" (#164)
Add `componentPathExtraDepth` config key. Add tests. Document new configuration key.
1 parent ec59f1d commit 8d68840

File tree

5 files changed

+64
-8
lines changed

5 files changed

+64
-8
lines changed

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,10 +169,9 @@ See [here](docs/observability.md)
169169
To publish container images from a forked repo set the `IMAGE_NAME` and `REGISTRY` GitHub Action Repository variables to use GitHub packages.
170170
`REGISTRY` should be `ghcr.io` and `IMAGE_NAME` should match the repository slug, like so:
171171
like so:
172+
<!-- markdownlint-disable MD033 -->
172173
<img width="785" alt="image" src="https://github.com/commercetools/telefonistka/assets/1616153/2f7201d6-fdb2-4cbf-8705-d6da7f4f6e80">
173-
174-
175-
174+
<!-- markdownlint-enable MD033 -->
176175

177176
## Roadmap
178177

docs/installation.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,12 @@ Pulled from `telefonistka.yaml` file in the repo root directory(default branch)
8585

8686
Configuration keys:
8787

88+
<!-- markdownlint-disable MD033 -->
8889
|key|desc|
8990
|---|---|
9091
|`promotionPaths`| Array of maps, each map describes a promotion flow|
9192
|`promotionPaths[0].sourcePath`| directory that holds components(subdirectories) to be synced, can include a regex.|
93+
|`promotionPaths[0].componentPathExtraDepth`| The number of extra nesting levels to add to the "components" being promoted, this allows nesting components in subdirectories while keeping them distinct.<br>A `2` value will mean the component name includes the 3 subdirectories under the `sourcePath`|
9294
|`promotionPaths[0].conditions` | conditions for triggering a specific promotion flows. Flows are evaluated in order, first one to match is triggered.|
9395
|`promotionPaths[0].conditions.prHasLabels` | Array of PR labels, if the triggering PR has any of these labels the condition is considered fulfilled.|
9496
|`promotionPaths[0].conditions.autoMerge`| Boolean value. If set to true, PR will be automatically merged after it is created.|
@@ -97,6 +99,7 @@ Configuration keys:
9799
|`dryRunMode`| if true, the bot will just comment the planned promotion on the merged PR|
98100
|`autoApprovePromotionPrs`| if true the bot will auto-approve all promotion PRs, with the assumption the original PR was peer reviewed and is promoted verbatim. Required additional GH token via APPROVER_GITHUB_OAUTH_TOKEN env variable|
99101
|`toggleCommitStatus`| Map of strings, allow (non-repo-admin) users to change the [Github commit status](https://docs.github.com/en/rest/commits/statuses) state(from failure to success and back). This can be used to continue promotion of a change that doesn't pass repo checks. the keys are strings commented in the PRs, values are [Github commit status context](https://docs.github.com/en/rest/commits/statuses?apiVersion=2022-11-28#create-a-commit-status) to be overridden|
102+
<!-- markdownlint-enable MD033 -->
100103

101104
Example:
102105

internal/pkg/configuration/config.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@ type PromotionPr struct {
2424
}
2525

2626
type PromotionPath struct {
27-
Conditions Condition `yaml:"conditions"`
28-
SourcePath string `yaml:"sourcePath"`
29-
PromotionPrs []PromotionPr `yaml:"promotionPrs"`
27+
Conditions Condition `yaml:"conditions"`
28+
ComponentPathExtraDepth int `yaml:"componentPathExtraDepth"`
29+
SourcePath string `yaml:"sourcePath"`
30+
PromotionPrs []PromotionPr `yaml:"promotionPrs"`
3031
}
3132

3233
type Config struct {

internal/pkg/githubapi/promotion.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,12 +132,18 @@ func GeneratePromotionPlan(ghPrClientDetails GhPrClientDetails, config *cfg.Conf
132132
for _, promotionPathConfig := range config.PromotionPaths {
133133
if match, _ := regexp.MatchString("^"+promotionPathConfig.SourcePath+".*", *changedFile.Filename); match {
134134
// "components" here are the sub directories of the SourcePath
135-
getComponentRegexString := regexp.MustCompile("^" + promotionPathConfig.SourcePath + "([^/]*)/.*")
135+
// but with promotionPathConfig.ComponentPathExtraDepth we can grab multiple levels of subdirectories,
136+
// to support cases where components are nested deeper(e.g. [SourcePath]/owningTeam/namespace/component1)
137+
componentPathRegexSubSstrings := []string{}
138+
for i := 0; i <= promotionPathConfig.ComponentPathExtraDepth; i++ {
139+
componentPathRegexSubSstrings = append(componentPathRegexSubSstrings, "[^/]*")
140+
}
141+
componentPathRegexSubString := strings.Join(componentPathRegexSubSstrings, "/")
142+
getComponentRegexString := regexp.MustCompile("^" + promotionPathConfig.SourcePath + "(" + componentPathRegexSubString + ")/.*")
136143
componentName := getComponentRegexString.ReplaceAllString(*changedFile.Filename, "${1}")
137144

138145
getSourcePathRegexString := regexp.MustCompile("^(" + promotionPathConfig.SourcePath + ")" + componentName + "/.*")
139146
compiledSourcePath := getSourcePathRegexString.ReplaceAllString(*changedFile.Filename, "${1}")
140-
141147
relevantComponentsElement := relevantComponent{
142148
SourcePath: compiledSourcePath,
143149
ComponentName: componentName,

internal/pkg/githubapi/promotion_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,3 +434,50 @@ func TestGeneratePromotionPlanTwoComponents(t *testing.T) {
434434
)
435435
generatePromotionPlanTestHelper(t, config, expectedPromotion, mockedHTTPClient)
436436
}
437+
438+
func TestGenerateNestedSourceRegexPromotionPlan(t *testing.T) {
439+
t.Parallel()
440+
config := &cfg.Config{
441+
PromotionPaths: []cfg.PromotionPath{
442+
{
443+
SourcePath: "prod/us-east-4/",
444+
ComponentPathExtraDepth: 2,
445+
PromotionPrs: []cfg.PromotionPr{
446+
{
447+
TargetPaths: []string{
448+
"prod/eu-west-1/",
449+
},
450+
},
451+
},
452+
},
453+
},
454+
}
455+
expectedPromotion := map[string]PromotionInstance{
456+
"prod/us-east-4/>prod/eu-west-1/": {
457+
ComputedSyncPaths: map[string]string{
458+
"prod/eu-west-1/teamA/namespaceB/componentA": "prod/us-east-4/teamA/namespaceB/componentA",
459+
},
460+
},
461+
}
462+
463+
mockedHTTPClient := mock.NewMockedHTTPClient(
464+
mock.WithRequestMatch(
465+
mock.GetReposPullsFilesByOwnerByRepoByPullNumber,
466+
[]github.CommitFile{
467+
{Filename: github.String("prod/us-east-4/teamA/namespaceB/componentA/file.yaml")},
468+
{Filename: github.String("prod/us-east-4/teamA/namespaceB/componentA/aSubDir/file3.yaml")},
469+
},
470+
),
471+
mock.WithRequestMatchHandler(
472+
mock.GetReposContentsByOwnerByRepoByPath,
473+
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
474+
mock.WriteError(
475+
w,
476+
http.StatusNotFound,
477+
"no *optional* in-component telefonistka config file",
478+
)
479+
}),
480+
),
481+
)
482+
generatePromotionPlanTestHelper(t, config, expectedPromotion, mockedHTTPClient)
483+
}

0 commit comments

Comments
 (0)