Skip to content

Commit

Permalink
Support multiple levels of nesting for promoted "components" (#164)
Browse files Browse the repository at this point in the history
Add `componentPathExtraDepth` config key.
Add tests.
Document new configuration key.
  • Loading branch information
Oded-B authored Apr 8, 2024
1 parent ec59f1d commit 8d68840
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 8 deletions.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,9 @@ See [here](docs/observability.md)
To publish container images from a forked repo set the `IMAGE_NAME` and `REGISTRY` GitHub Action Repository variables to use GitHub packages.
`REGISTRY` should be `ghcr.io` and `IMAGE_NAME` should match the repository slug, like so:
like so:
<!-- markdownlint-disable MD033 -->
<img width="785" alt="image" src="https://github.com/commercetools/telefonistka/assets/1616153/2f7201d6-fdb2-4cbf-8705-d6da7f4f6e80">



<!-- markdownlint-enable MD033 -->

## Roadmap

Expand Down
3 changes: 3 additions & 0 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,12 @@ Pulled from `telefonistka.yaml` file in the repo root directory(default branch)

Configuration keys:

<!-- markdownlint-disable MD033 -->
|key|desc|
|---|---|
|`promotionPaths`| Array of maps, each map describes a promotion flow|
|`promotionPaths[0].sourcePath`| directory that holds components(subdirectories) to be synced, can include a regex.|
|`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`|
|`promotionPaths[0].conditions` | conditions for triggering a specific promotion flows. Flows are evaluated in order, first one to match is triggered.|
|`promotionPaths[0].conditions.prHasLabels` | Array of PR labels, if the triggering PR has any of these labels the condition is considered fulfilled.|
|`promotionPaths[0].conditions.autoMerge`| Boolean value. If set to true, PR will be automatically merged after it is created.|
Expand All @@ -97,6 +99,7 @@ Configuration keys:
|`dryRunMode`| if true, the bot will just comment the planned promotion on the merged PR|
|`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|
|`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|
<!-- markdownlint-enable MD033 -->

Example:

Expand Down
7 changes: 4 additions & 3 deletions internal/pkg/configuration/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ type PromotionPr struct {
}

type PromotionPath struct {
Conditions Condition `yaml:"conditions"`
SourcePath string `yaml:"sourcePath"`
PromotionPrs []PromotionPr `yaml:"promotionPrs"`
Conditions Condition `yaml:"conditions"`
ComponentPathExtraDepth int `yaml:"componentPathExtraDepth"`
SourcePath string `yaml:"sourcePath"`
PromotionPrs []PromotionPr `yaml:"promotionPrs"`
}

type Config struct {
Expand Down
10 changes: 8 additions & 2 deletions internal/pkg/githubapi/promotion.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,18 @@ func GeneratePromotionPlan(ghPrClientDetails GhPrClientDetails, config *cfg.Conf
for _, promotionPathConfig := range config.PromotionPaths {
if match, _ := regexp.MatchString("^"+promotionPathConfig.SourcePath+".*", *changedFile.Filename); match {
// "components" here are the sub directories of the SourcePath
getComponentRegexString := regexp.MustCompile("^" + promotionPathConfig.SourcePath + "([^/]*)/.*")
// but with promotionPathConfig.ComponentPathExtraDepth we can grab multiple levels of subdirectories,
// to support cases where components are nested deeper(e.g. [SourcePath]/owningTeam/namespace/component1)
componentPathRegexSubSstrings := []string{}
for i := 0; i <= promotionPathConfig.ComponentPathExtraDepth; i++ {
componentPathRegexSubSstrings = append(componentPathRegexSubSstrings, "[^/]*")
}
componentPathRegexSubString := strings.Join(componentPathRegexSubSstrings, "/")
getComponentRegexString := regexp.MustCompile("^" + promotionPathConfig.SourcePath + "(" + componentPathRegexSubString + ")/.*")
componentName := getComponentRegexString.ReplaceAllString(*changedFile.Filename, "${1}")

getSourcePathRegexString := regexp.MustCompile("^(" + promotionPathConfig.SourcePath + ")" + componentName + "/.*")
compiledSourcePath := getSourcePathRegexString.ReplaceAllString(*changedFile.Filename, "${1}")

relevantComponentsElement := relevantComponent{
SourcePath: compiledSourcePath,
ComponentName: componentName,
Expand Down
47 changes: 47 additions & 0 deletions internal/pkg/githubapi/promotion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -434,3 +434,50 @@ func TestGeneratePromotionPlanTwoComponents(t *testing.T) {
)
generatePromotionPlanTestHelper(t, config, expectedPromotion, mockedHTTPClient)
}

func TestGenerateNestedSourceRegexPromotionPlan(t *testing.T) {
t.Parallel()
config := &cfg.Config{
PromotionPaths: []cfg.PromotionPath{
{
SourcePath: "prod/us-east-4/",
ComponentPathExtraDepth: 2,
PromotionPrs: []cfg.PromotionPr{
{
TargetPaths: []string{
"prod/eu-west-1/",
},
},
},
},
},
}
expectedPromotion := map[string]PromotionInstance{
"prod/us-east-4/>prod/eu-west-1/": {
ComputedSyncPaths: map[string]string{
"prod/eu-west-1/teamA/namespaceB/componentA": "prod/us-east-4/teamA/namespaceB/componentA",
},
},
}

mockedHTTPClient := mock.NewMockedHTTPClient(
mock.WithRequestMatch(
mock.GetReposPullsFilesByOwnerByRepoByPullNumber,
[]github.CommitFile{
{Filename: github.String("prod/us-east-4/teamA/namespaceB/componentA/file.yaml")},
{Filename: github.String("prod/us-east-4/teamA/namespaceB/componentA/aSubDir/file3.yaml")},
},
),
mock.WithRequestMatchHandler(
mock.GetReposContentsByOwnerByRepoByPath,
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mock.WriteError(
w,
http.StatusNotFound,
"no *optional* in-component telefonistka config file",
)
}),
),
)
generatePromotionPlanTestHelper(t, config, expectedPromotion, mockedHTTPClient)
}

0 comments on commit 8d68840

Please sign in to comment.