WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content
This repository was archived by the owner on Dec 10, 2025. It is now read-only.

Commit f03ef81

Browse files
committed
refactor: allow gh merge and retry values to be configurable
1 parent 44baafe commit f03ef81

File tree

4 files changed

+235
-14
lines changed

4 files changed

+235
-14
lines changed
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# Configuration for copying code examples from aggregation-tasks repository
2+
# Repository: https://github.com/cbullinger/aggregation-tasks
3+
#
4+
# This repository contains MongoDB aggregation pipeline examples in multiple languages:
5+
# - java/
6+
# - nodejs/
7+
# - python/
8+
9+
source_repo: "cbullinger/aggregation-tasks"
10+
source_branch: "main"
11+
12+
copy_rules:
13+
# Rule 1: Match all language directories with regex pattern
14+
# This pattern extracts the language name and file path
15+
- name: "aggregation-language-examples"
16+
source_pattern:
17+
type: "regex"
18+
pattern: "^(?P<lang>java|nodejs|python)/(?P<file>.+)$"
19+
targets:
20+
- repo: "mongodb/docs"
21+
branch: "main"
22+
path_transform: "source/code-examples/aggregation/${lang}/${file}"
23+
commit_strategy:
24+
type: "pull_request"
25+
pr_title: "Update ${lang} aggregation examples"
26+
pr_body: |
27+
Automated update of ${lang} aggregation pipeline examples
28+
29+
**Details:**
30+
- Rule: ${rule_name}
31+
- Source: ${source_repo}
32+
- Files updated: ${file_count}
33+
auto_merge: false
34+
deprecation_check:
35+
enabled: true
36+
file: "deprecated_examples.json"
37+
38+
# Rule 2: Match specific language - Java only
39+
- name: "java-aggregation-examples"
40+
source_pattern:
41+
type: "regex"
42+
pattern: "^java/(?P<file>.+\\.java)$"
43+
targets:
44+
- repo: "mongodb/docs-java"
45+
branch: "main"
46+
path_transform: "source/examples/aggregation/${file}"
47+
commit_strategy:
48+
type: "pull_request"
49+
pr_title: "Update Java aggregation examples"
50+
pr_body: "Automated update of Java aggregation pipeline examples"
51+
auto_merge: false
52+
53+
# Rule 3: Match specific language - Node.js only
54+
- name: "nodejs-aggregation-examples"
55+
source_pattern:
56+
type: "regex"
57+
pattern: "^nodejs/(?P<file>.+\\.(js|ts))$"
58+
targets:
59+
- repo: "mongodb/docs-node"
60+
branch: "main"
61+
path_transform: "source/examples/aggregation/${file}"
62+
commit_strategy:
63+
type: "pull_request"
64+
pr_title: "Update Node.js aggregation examples"
65+
pr_body: "Automated update of Node.js aggregation pipeline examples"
66+
auto_merge: false
67+
68+
# Rule 4: Match specific language - Python only
69+
- name: "python-aggregation-examples"
70+
source_pattern:
71+
type: "regex"
72+
pattern: "^python/(?P<file>.+\\.py)$"
73+
targets:
74+
- repo: "mongodb/docs-python"
75+
branch: "main"
76+
path_transform: "source/examples/aggregation/${file}"
77+
commit_strategy:
78+
type: "pull_request"
79+
pr_title: "Update Python aggregation examples"
80+
pr_body: "Automated update of Python aggregation pipeline examples"
81+
auto_merge: false
82+
83+
# Rule 5: Alternative pattern - match with subdirectories
84+
# Use this if the repository structure has subdirectories within language folders
85+
- name: "aggregation-with-subdirs"
86+
source_pattern:
87+
type: "regex"
88+
pattern: "^(?P<lang>java|nodejs|python)/(?P<subdir>[^/]+)/(?P<file>.+)$"
89+
targets:
90+
- repo: "mongodb/docs"
91+
branch: "main"
92+
path_transform: "source/code-examples/aggregation/${lang}/${subdir}/${file}"
93+
commit_strategy:
94+
type: "pull_request"
95+
pr_title: "Update ${lang} aggregation examples (${subdir})"
96+
pr_body: |
97+
Automated update of ${lang} aggregation examples
98+
Category: ${subdir}
99+
Files updated: ${file_count}
100+
auto_merge: false
101+
102+
# Rule 6: Glob pattern alternative - simpler but less flexible
103+
- name: "all-java-files-glob"
104+
source_pattern:
105+
type: "glob"
106+
pattern: "java/**/*.java"
107+
targets:
108+
- repo: "mongodb/docs"
109+
branch: "main"
110+
path_transform: "source/code-examples/aggregation/java/${filename}"
111+
commit_strategy:
112+
type: "direct"
113+
commit_message: "Update Java aggregation examples"
114+
115+
# Rule 7: Prefix pattern - simplest approach
116+
- name: "all-languages-prefix"
117+
source_pattern:
118+
type: "prefix"
119+
pattern: "java"
120+
targets:
121+
- repo: "mongodb/docs"
122+
branch: "main"
123+
path_transform: "source/code-examples/aggregation/${relative_path}"
124+
commit_strategy:
125+
type: "direct"
126+
commit_message: "Update aggregation examples from ${source_repo}"
127+

examples-copier/configs/env.yaml.example

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,27 @@ env_variables:
111111
# DEFAULT BEHAVIORS (OPTIONAL)
112112
# =============================================================================
113113
# System-wide defaults that individual config rules can override
114-
114+
115115
DEFAULT_RECURSIVE_COPY: "true" # Default recursive copy behavior (default: true)
116116
DEFAULT_PR_MERGE: "false" # Default auto-merge PRs without review (default: false)
117117
DEFAULT_COMMIT_MESSAGE: "Automated PR with updated examples" # Default commit message (default: shown)
118+
119+
# =============================================================================
120+
# GITHUB API CONFIGURATION (OPTIONAL)
121+
# =============================================================================
122+
# Fine-tune GitHub API retry and polling behavior
123+
124+
# GitHub API Retry Configuration
125+
# Controls retry behavior when GitHub API calls fail due to eventual consistency
126+
# GITHUB_API_MAX_RETRIES: "3" # Number of retry attempts (default: 3)
127+
# GITHUB_API_INITIAL_RETRY_DELAY: "500" # Initial retry delay in milliseconds (default: 500)
128+
# # Uses exponential backoff: 500ms, 1s, 2s, etc.
129+
130+
# PR Merge Polling Configuration
131+
# Controls how long to wait for GitHub to compute PR mergeability
132+
# PR_MERGE_POLL_MAX_ATTEMPTS: "20" # Max polling attempts (default: 20)
133+
# PR_MERGE_POLL_INTERVAL: "500" # Polling interval in milliseconds (default: 500)
134+
# # Total wait time = attempts × interval (default: ~10 seconds)
118135

119136
# =============================================================================
120137
# TESTING / DEVELOPMENT OVERRIDES (DO NOT USE IN PRODUCTION)

examples-copier/configs/environment.go

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ type Config struct {
4747
SlackUsername string
4848
SlackIconEmoji string
4949
SlackEnabled bool
50+
51+
// GitHub API retry configuration
52+
GitHubAPIMaxRetries int
53+
GitHubAPIInitialRetryDelay int // in milliseconds
54+
55+
// PR merge polling configuration
56+
PRMergePollMaxAttempts int
57+
PRMergePollInterval int // in milliseconds
5058
}
5159

5260
const (
@@ -78,11 +86,15 @@ const (
7886
AuditDatabase = "AUDIT_DATABASE"
7987
AuditCollection = "AUDIT_COLLECTION"
8088
MetricsEnabled = "METRICS_ENABLED"
81-
SlackWebhookURL = "SLACK_WEBHOOK_URL"
82-
SlackChannel = "SLACK_CHANNEL"
83-
SlackUsername = "SLACK_USERNAME"
84-
SlackIconEmoji = "SLACK_ICON_EMOJI"
85-
SlackEnabled = "SLACK_ENABLED"
89+
SlackWebhookURL = "SLACK_WEBHOOK_URL"
90+
SlackChannel = "SLACK_CHANNEL"
91+
SlackUsername = "SLACK_USERNAME"
92+
SlackIconEmoji = "SLACK_ICON_EMOJI"
93+
SlackEnabled = "SLACK_ENABLED"
94+
GitHubAPIMaxRetries = "GITHUB_API_MAX_RETRIES"
95+
GitHubAPIInitialRetryDelay = "GITHUB_API_INITIAL_RETRY_DELAY"
96+
PRMergePollMaxAttempts = "PR_MERGE_POLL_MAX_ATTEMPTS"
97+
PRMergePollInterval = "PR_MERGE_POLL_INTERVAL"
8698
)
8799

88100
// NewConfig returns a new Config instance with default values
@@ -99,9 +111,13 @@ func NewConfig() *Config {
99111
WebhookSecretName: "projects/1054147886816/secrets/webhook-secret/versions/latest", // default webhook secret name for GCP Secret Manager
100112
CopierLogName: "copy-copier-log", // default log name for logging to GCP
101113
GoogleCloudProjectId: "github-copy-code-examples", // default project ID for logging to GCP
102-
DefaultRecursiveCopy: true, // system-wide default for recursive copying that individual config entries can override.
103-
DefaultPRMerge: false, // system-wide default for PR merge without review that individual config entries can override.
104-
DefaultCommitMessage: "Automated PR with updated examples", // default commit message used when per-config commit_message is absent.
114+
DefaultRecursiveCopy: true, // system-wide default for recursive copying that individual config entries can override.
115+
DefaultPRMerge: false, // system-wide default for PR merge without review that individual config entries can override.
116+
DefaultCommitMessage: "Automated PR with updated examples", // default commit message used when per-config commit_message is absent.
117+
GitHubAPIMaxRetries: 3, // default number of retry attempts for GitHub API calls
118+
GitHubAPIInitialRetryDelay: 500, // default initial retry delay in milliseconds (exponential backoff)
119+
PRMergePollMaxAttempts: 20, // default max attempts to poll PR for mergeability (~10 seconds with 500ms interval)
120+
PRMergePollInterval: 500, // default polling interval in milliseconds
105121
}
106122
}
107123

@@ -173,6 +189,14 @@ func LoadEnvironment(envFile string) (*Config, error) {
173189
config.SlackIconEmoji = getEnvWithDefault(SlackIconEmoji, ":robot_face:")
174190
config.SlackEnabled = getBoolEnvWithDefault(SlackEnabled, config.SlackWebhookURL != "")
175191

192+
// GitHub API retry configuration
193+
config.GitHubAPIMaxRetries = getIntEnvWithDefault(GitHubAPIMaxRetries, config.GitHubAPIMaxRetries)
194+
config.GitHubAPIInitialRetryDelay = getIntEnvWithDefault(GitHubAPIInitialRetryDelay, config.GitHubAPIInitialRetryDelay)
195+
196+
// PR merge polling configuration
197+
config.PRMergePollMaxAttempts = getIntEnvWithDefault(PRMergePollMaxAttempts, config.PRMergePollMaxAttempts)
198+
config.PRMergePollInterval = getIntEnvWithDefault(PRMergePollInterval, config.PRMergePollInterval)
199+
176200
// Export resolved values back into environment so downstream os.Getenv sees defaults
177201
_ = os.Setenv(Port, config.Port)
178202
_ = os.Setenv(RepoName, config.RepoName)
@@ -218,6 +242,19 @@ func getBoolEnvWithDefault(key string, defaultValue bool) bool {
218242
return strings.ToLower(value) == "true"
219243
}
220244

245+
// getIntEnvWithDefault returns the integer environment variable value or default if not set
246+
func getIntEnvWithDefault(key string, defaultValue int) int {
247+
value := os.Getenv(key)
248+
if value == "" {
249+
return defaultValue
250+
}
251+
var intValue int
252+
if _, err := fmt.Sscanf(value, "%d", &intValue); err != nil {
253+
return defaultValue
254+
}
255+
return intValue
256+
}
257+
221258
// validateConfig checks if all required configuration values are set
222259
func validateConfig(config *Config) error {
223260
var missingVars []string

examples-copier/services/github_write_to_target.go

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -165,11 +165,26 @@ func addFilesViaPR(ctx context.Context, client *github.Client, key UploadKey,
165165
LogInfo(fmt.Sprintf("PR URL: %s", pr.GetHTMLURL()))
166166
if mergeWithoutReview {
167167
// Poll PR for mergeability; GitHub may take a moment to compute it
168-
// We poll up to ~10s with 500ms interval
168+
// Get polling configuration from environment or use defaults
169+
cfg := configs.NewConfig()
170+
maxAttempts := cfg.PRMergePollMaxAttempts
171+
if envAttempts := os.Getenv(configs.PRMergePollMaxAttempts); envAttempts != "" {
172+
if parsed, err := parseIntWithDefault(envAttempts, maxAttempts); err == nil {
173+
maxAttempts = parsed
174+
}
175+
}
176+
177+
pollInterval := cfg.PRMergePollInterval
178+
if envInterval := os.Getenv(configs.PRMergePollInterval); envInterval != "" {
179+
if parsed, err := parseIntWithDefault(envInterval, pollInterval); err == nil {
180+
pollInterval = parsed
181+
}
182+
}
183+
169184
var mergeable *bool
170185
var mergeableState string
171186
owner, repoName := parseRepoPath(key.RepoName)
172-
for i := 0; i < 20; i++ {
187+
for i := 0; i < maxAttempts; i++ {
173188
current, _, gerr := client.PullRequests.Get(ctx, owner, repoName, pr.GetNumber())
174189
if gerr == nil && current != nil {
175190
mergeable = current.Mergeable
@@ -178,7 +193,7 @@ func addFilesViaPR(ctx context.Context, client *github.Client, key UploadKey,
178193
break
179194
}
180195
}
181-
time.Sleep(500 * time.Millisecond)
196+
time.Sleep(time.Duration(pollInterval) * time.Millisecond)
182197
}
183198
if mergeable != nil && !*mergeable || strings.EqualFold(mergeableState, "dirty") {
184199
LogWarning(fmt.Sprintf("PR #%d is not mergeable (state=%s). Likely merge conflicts. Leaving PR open for manual resolution.", pr.GetNumber(), mergeableState))
@@ -269,8 +284,24 @@ func createCommitTree(ctx context.Context, client *github.Client, targetBranch U
269284
// 1) Get current ref with retry logic to handle GitHub API eventual consistency
270285
// When a branch is just created, it may take a moment to be visible
271286
var ref *github.Reference
272-
maxRetries := 3
273-
retryDelay := 500 * time.Millisecond
287+
288+
// Get retry configuration from environment or use defaults
289+
cfg := configs.NewConfig()
290+
maxRetries := cfg.GitHubAPIMaxRetries
291+
if envRetries := os.Getenv(configs.GitHubAPIMaxRetries); envRetries != "" {
292+
if parsed, err := parseIntWithDefault(envRetries, maxRetries); err == nil {
293+
maxRetries = parsed
294+
}
295+
}
296+
297+
initialRetryDelay := cfg.GitHubAPIInitialRetryDelay
298+
if envDelay := os.Getenv(configs.GitHubAPIInitialRetryDelay); envDelay != "" {
299+
if parsed, err := parseIntWithDefault(envDelay, initialRetryDelay); err == nil {
300+
initialRetryDelay = parsed
301+
}
302+
}
303+
304+
retryDelay := time.Duration(initialRetryDelay) * time.Millisecond
274305

275306
for attempt := 1; attempt <= maxRetries; attempt++ {
276307
ref, _, err = client.Git.GetRef(ctx, owner, repoName, targetBranch.BranchPath)
@@ -401,3 +432,12 @@ func deleteBranchIfExists(backgroundContext context.Context, client *github.Clie
401432
func DeleteBranchIfExistsExported(ctx context.Context, client *github.Client, repo string, ref *github.Reference) {
402433
deleteBranchIfExists(ctx, client, repo, ref)
403434
}
435+
436+
// parseIntWithDefault parses a string to int, returning defaultValue on error
437+
func parseIntWithDefault(s string, defaultValue int) (int, error) {
438+
var result int
439+
if _, err := fmt.Sscanf(s, "%d", &result); err != nil {
440+
return defaultValue, err
441+
}
442+
return result, nil
443+
}

0 commit comments

Comments
 (0)