diff --git a/README.md b/README.md
index b9b4679..a14b2d9 100644
--- a/README.md
+++ b/README.md
@@ -169,7 +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:
+
+
## Roadmap
diff --git a/docs/installation.md b/docs/installation.md
index e5611ba..db097c6 100644
--- a/docs/installation.md
+++ b/docs/installation.md
@@ -85,10 +85,12 @@ Pulled from `telefonistka.yaml` file in the repo root directory(default branch)
Configuration keys:
+
|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.
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.|
@@ -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|
+
Example:
diff --git a/internal/pkg/configuration/config.go b/internal/pkg/configuration/config.go
index c9fa292..4018f67 100644
--- a/internal/pkg/configuration/config.go
+++ b/internal/pkg/configuration/config.go
@@ -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 {
diff --git a/internal/pkg/githubapi/promotion.go b/internal/pkg/githubapi/promotion.go
index 71bd96d..b2f8211 100644
--- a/internal/pkg/githubapi/promotion.go
+++ b/internal/pkg/githubapi/promotion.go
@@ -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,
diff --git a/internal/pkg/githubapi/promotion_test.go b/internal/pkg/githubapi/promotion_test.go
index 8743d9f..411171a 100644
--- a/internal/pkg/githubapi/promotion_test.go
+++ b/internal/pkg/githubapi/promotion_test.go
@@ -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)
+}