From 96b720776ba5af3bdb3f24fb38fa991d82f1b8fc Mon Sep 17 00:00:00 2001 From: Oded Ben Ozer Date: Tue, 2 Apr 2024 16:43:47 +0200 Subject: [PATCH 01/10] Allow nesting of components in subdirs --- internal/pkg/configuration/config.go | 7 ++-- internal/pkg/githubapi/promotion.go | 9 ++++- internal/pkg/githubapi/promotion_test.go | 47 ++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/internal/pkg/configuration/config.go b/internal/pkg/configuration/config.go index 6668feed..ce665eb0 100644 --- a/internal/pkg/configuration/config.go +++ b/internal/pkg/configuration/config.go @@ -23,9 +23,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:"componentPathDepth"` + 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 e64a0c56..753695c7 100644 --- a/internal/pkg/githubapi/promotion.go +++ b/internal/pkg/githubapi/promotion.go @@ -139,7 +139,14 @@ 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 + "/.*") diff --git a/internal/pkg/githubapi/promotion_test.go b/internal/pkg/githubapi/promotion_test.go index f9fed2ba..d8e332fa 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) +} From 506b8a0a6f5a4c73dfee2dca1a277f1e2cc86812 Mon Sep 17 00:00:00 2001 From: Oded Ben Ozer Date: Wed, 3 Apr 2024 12:59:20 +0200 Subject: [PATCH 02/10] Document new configuration key --- docs/installation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/installation.md b/docs/installation.md index a9d389cf..e7dd87c7 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -89,6 +89,7 @@ Configuration keys: |---|---| |`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 | |`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. Currently it's the only supported condition type| |`promotionPaths[0].promotionPrs`| Array of structures, each element represent a PR that will be opened when files are changed under `sourcePath`. Multiple elements means multiple PR will be opened| From 2e2a2b2a2bd378d02780a01ef9cc0a8b22bf638f Mon Sep 17 00:00:00 2001 From: Oded Ben Ozer Date: Mon, 8 Apr 2024 11:28:05 +0200 Subject: [PATCH 03/10] Add some debug logs --- internal/pkg/githubapi/promotion.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/pkg/githubapi/promotion.go b/internal/pkg/githubapi/promotion.go index 753695c7..f938e332 100644 --- a/internal/pkg/githubapi/promotion.go +++ b/internal/pkg/githubapi/promotion.go @@ -145,9 +145,11 @@ func GeneratePromotionPlan(ghPrClientDetails GhPrClientDetails, config *cfg.Conf for i := 0; i <= promotionPathConfig.ComponentPathExtraDepth; i++ { componentPathRegexSubSstrings = append(componentPathRegexSubSstrings, "[^/]*") } + ghPrClientDetails.PrLogger.Debugf("componentPathRegexSubSstrings: %s", componentPathRegexSubSstrings) componentPathRegexSubString := strings.Join(componentPathRegexSubSstrings, "/") getComponentRegexString := regexp.MustCompile("^" + promotionPathConfig.SourcePath + "(" + componentPathRegexSubString + ")/.*") componentName := getComponentRegexString.ReplaceAllString(*changedFile.Filename, "${1}") + ghPrClientDetails.PrLogger.Debugf("componentName: %s", componentName) getSourcePathRegexString := regexp.MustCompile("^(" + promotionPathConfig.SourcePath + ")" + componentName + "/.*") compiledSourcePath := getSourcePathRegexString.ReplaceAllString(*changedFile.Filename, "${1}") From 1b35c2ce397f8a325aeef534e5051b4375db2c73 Mon Sep 17 00:00:00 2001 From: Oded Ben Ozer Date: Mon, 8 Apr 2024 12:17:41 +0200 Subject: [PATCH 04/10] Match yaml key name to config --- internal/pkg/configuration/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/configuration/config.go b/internal/pkg/configuration/config.go index ce665eb0..392677a7 100644 --- a/internal/pkg/configuration/config.go +++ b/internal/pkg/configuration/config.go @@ -24,7 +24,7 @@ type PromotionPr struct { type PromotionPath struct { Conditions Condition `yaml:"conditions"` - ComponentPathExtraDepth int `yaml:"componentPathDepth"` + ComponentPathExtraDepth int `yaml:"componentPathExtraDepth"` SourcePath string `yaml:"sourcePath"` PromotionPrs []PromotionPr `yaml:"promotionPrs"` } From 79b733211d5dea8d041ef3f6781584195f95bbc3 Mon Sep 17 00:00:00 2001 From: Oded Ben Ozer Date: Mon, 8 Apr 2024 13:34:38 +0200 Subject: [PATCH 05/10] Remove unneeded debug messages --- internal/pkg/githubapi/promotion.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/internal/pkg/githubapi/promotion.go b/internal/pkg/githubapi/promotion.go index f938e332..ba6993c5 100644 --- a/internal/pkg/githubapi/promotion.go +++ b/internal/pkg/githubapi/promotion.go @@ -145,15 +145,12 @@ func GeneratePromotionPlan(ghPrClientDetails GhPrClientDetails, config *cfg.Conf for i := 0; i <= promotionPathConfig.ComponentPathExtraDepth; i++ { componentPathRegexSubSstrings = append(componentPathRegexSubSstrings, "[^/]*") } - ghPrClientDetails.PrLogger.Debugf("componentPathRegexSubSstrings: %s", componentPathRegexSubSstrings) componentPathRegexSubString := strings.Join(componentPathRegexSubSstrings, "/") getComponentRegexString := regexp.MustCompile("^" + promotionPathConfig.SourcePath + "(" + componentPathRegexSubString + ")/.*") componentName := getComponentRegexString.ReplaceAllString(*changedFile.Filename, "${1}") - ghPrClientDetails.PrLogger.Debugf("componentName: %s", componentName) getSourcePathRegexString := regexp.MustCompile("^(" + promotionPathConfig.SourcePath + ")" + componentName + "/.*") compiledSourcePath := getSourcePathRegexString.ReplaceAllString(*changedFile.Filename, "${1}") - relevantComponentsElement := relevantComponent{ SourcePath: compiledSourcePath, ComponentName: componentName, From 980c8644641a5c7829eb6521b9fd01640e782681 Mon Sep 17 00:00:00 2001 From: Oded Ben Ozer Date: Mon, 8 Apr 2024 13:39:48 +0200 Subject: [PATCH 06/10] Fix documentation --- docs/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.md b/docs/installation.md index e7dd87c7..533ea97e 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -89,7 +89,7 @@ Configuration keys: |---|---| |`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 | +|`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. Currently it's the only supported condition type| |`promotionPaths[0].promotionPrs`| Array of structures, each element represent a PR that will be opened when files are changed under `sourcePath`. Multiple elements means multiple PR will be opened| From 66f8aed74dd4d003985d3e6373b2d1d925bf4f93 Mon Sep 17 00:00:00 2001 From: Oded Ben Ozer Date: Mon, 8 Apr 2024 13:47:58 +0200 Subject: [PATCH 07/10] Exclude markdown lint rule (I want that newline!) --- docs/installation.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/installation.md b/docs/installation.md index 533ea97e..02e98195 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -89,7 +89,9 @@ Configuration keys: |---|---| |`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. Currently it's the only supported condition type| |`promotionPaths[0].promotionPrs`| Array of structures, each element represent a PR that will be opened when files are changed under `sourcePath`. Multiple elements means multiple PR will be opened| From 6d2086078a3f40ff8c70e9f441531fdd5ff11828 Mon Sep 17 00:00:00 2001 From: Oded Ben Ozer Date: Mon, 8 Apr 2024 13:49:36 +0200 Subject: [PATCH 08/10] Fix syntax issue --- docs/installation.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index 02e98195..344f3118 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -89,9 +89,7 @@ Configuration keys: |---|---| |`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].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. Currently it's the only supported condition type| |`promotionPaths[0].promotionPrs`| Array of structures, each element represent a PR that will be opened when files are changed under `sourcePath`. Multiple elements means multiple PR will be opened| From 382d4e7891db4f30611ab336fd04c5287d567cfe Mon Sep 17 00:00:00 2001 From: Oded Ben Ozer Date: Mon, 8 Apr 2024 13:56:56 +0200 Subject: [PATCH 09/10] Markdown syntax --- docs/installation.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/installation.md b/docs/installation.md index 344f3118..bdac47ce 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -85,11 +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].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. Currently it's the only supported condition type| |`promotionPaths[0].promotionPrs`| Array of structures, each element represent a PR that will be opened when files are changed under `sourcePath`. Multiple elements means multiple PR will be opened| @@ -97,6 +98,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: From 43fb7e43f3236490cdc1c26e3d6417a7a01401e6 Mon Sep 17 00:00:00 2001 From: Oded Ben Ozer Date: Mon, 8 Apr 2024 14:13:48 +0200 Subject: [PATCH 10/10] Linter work --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b9b46795..a14b2d9f 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: + image + ## Roadmap