Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/ce/howto/include-exclude-patterns.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ modules/

If you wanted to trigger plans for all `modules/` folder in both dev and prod projects you would include them in the `include_patterns` key. Similarly you put anything which you want to ignore in the `exclude_patterns` key ( exclude takes precedence over includes).

If you want a narrower, dependency-graph-based trigger instead of a broad `modules/**` glob, you can enable `dependency_file_triggers: true`. For Terraform/OpenTofu this follows local `module` imports and tracks imported module `*.tf*` files recursively. If those imported files belong to another Digger project, Digger also infers the matching project dependency edge so you do not need to duplicate `depends_on` as often. This setting is currently intended for Terraform/OpenTofu projects.

Example using patterns relative to project directory:

```yml
Expand All @@ -46,4 +48,4 @@ projects:
dir: ./production
include_patterns: ["modules/**"]
exclude_patterns: ["modules/dev_only_module/**"]
```
```
16 changes: 16 additions & 0 deletions docs/ce/reference/digger.yml.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ projects:
layer: 0
include_patterns: ["../modules/**"]
exclude_patterns: []
dependency_file_triggers: false
depends_on: []
aws_role_to_assume:
aws_role_region: us-east-1
Expand All @@ -76,9 +77,11 @@ projects:
workflow: staging
include_patterns: ["../modules/**"]
exclude_patterns: []
dependency_file_triggers: false
generate_projects:
include: "../projects/**"
exclude: "../.terraform/**"
dependency_file_triggers: false
terragrunt: false
blocks:
- block_name: dev-block
Expand All @@ -90,6 +93,7 @@ generate_projects:
terragrunt: false
include_patterns: []
exclude_patterns: []
dependency_file_triggers: false
aws_role_to_assume:
aws_role_region: us-east-1
state: arn:aws:iam::123456789012:role/state-role
Expand Down Expand Up @@ -321,6 +325,10 @@ Define individual projects using the `projects` array.
List of directory glob patterns to exclude, e.g., `[".terraform/**"]`. See [Include / Exclude Patterns](/ce/howto/include-exclude-patterns).
</ParamField>

<ParamField path="dependency_file_triggers" type="boolean" default="false">
Automatically derive additional trigger paths from the project dependency graph for Terraform/OpenTofu projects. This follows local `module` sources and watches imported module `*.tf*` files recursively. When imported files belong to another Digger project, this also infers a project dependency edge, reducing the need to duplicate `depends_on`. This augments `include_patterns`; it does not replace them. Currently not supported for Terragrunt or Pulumi projects.
</ParamField>

<ParamField path="depends_on" type="array" default="[]">
List of project names that must complete before this project. Does not force terraform run, but affects the order of commands for projects modified in the current PR.
</ParamField>
Expand Down Expand Up @@ -377,6 +385,10 @@ Automatically generate projects from directory structure using the `generate_pro
Glob pattern to exclude directories from project generation.
</ParamField>

<ParamField path="dependency_file_triggers" type="boolean" default="false">
Enable automatic dependency-based trigger discovery for generated Terraform/OpenTofu projects. If imported files belong to another generated or declared Digger project, Digger also infers the corresponding project dependency edge. This augments any `include_patterns` set on the generated projects. Currently not supported for Terragrunt generation.
</ParamField>

<ParamField path="terragrunt" type="boolean" default="false">
Whether to use Terragrunt for generated projects.
</ParamField>
Expand Down Expand Up @@ -421,6 +433,10 @@ Automatically generate projects from directory structure using the `generate_pro
List of directory glob patterns to exclude from generated projects.
</ParamField>

<ParamField path="dependency_file_triggers" type="boolean" default="false">
Enable automatic dependency-based trigger discovery for Terraform/OpenTofu projects generated from this block. If imported files belong to another Digger project, Digger also infers the corresponding project dependency edge. Currently not supported for Terragrunt blocks.
</ParamField>

<ParamField path="workflow" type="string" default="default">
Workflow to use for projects in this block.
</ParamField>
Expand Down
35 changes: 35 additions & 0 deletions libs/ci/generic/comment_filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package generic
import (
"fmt"
"github.com/diggerhq/digger/libs/digger_config"
"github.com/dominikbraun/graph"
"github.com/samber/lo"
)

Expand All @@ -21,6 +22,40 @@ func FilterTargetBranchForImpactedProjects(impactedProjects []digger_config.Proj
return impactedProjects
}

func CreateTargetBranchDependencyGraph(projects []digger_config.Project, defaultBranch string, targetBranch string) (graph.Graph[string, digger_config.Project], error) {
filteredProjects := FilterTargetBranchForImpactedProjects(projects, defaultBranch, targetBranch)
allowedProjects := make(map[string]struct{}, len(filteredProjects))
for _, project := range filteredProjects {
allowedProjects[project.Name] = struct{}{}
}

for i := range filteredProjects {
project := &filteredProjects[i]

filteredDependencies := make([]string, 0, len(project.DependencyProjects))
for _, dependencyProject := range project.DependencyProjects {
if _, ok := allowedProjects[dependencyProject]; ok {
filteredDependencies = append(filteredDependencies, dependencyProject)
}
}
project.DependencyProjects = filteredDependencies

if len(project.InferredDependencyPatternsByProject) == 0 {
continue
}

filteredPatternsByProject := make(map[string][]string)
for dependencyProject, patterns := range project.InferredDependencyPatternsByProject {
if _, ok := allowedProjects[dependencyProject]; ok {
filteredPatternsByProject[dependencyProject] = patterns
}
}
project.InferredDependencyPatternsByProject = filteredPatternsByProject
}

return digger_config.CreateProjectDependencyGraph(filteredProjects)
}

func FilterOutProjectsFromComment(impactedProjects []digger_config.Project, comment string) ([]digger_config.Project, error) {
var filteredProjects []digger_config.Project
commentParts, valid, err := ParseDiggerCommentFlags(comment)
Expand Down
26 changes: 23 additions & 3 deletions libs/ci/generic/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func ProcessIssueCommentEvent(prNumber int, diggerConfig *digger_config.DiggerCo
impactedProjects, impactedProjectsSourceMapping := diggerConfig.GetModifiedProjects(changedFiles)

if diggerConfig.DependencyConfiguration.Mode == digger_config.DependencyConfigurationHard {
impactedProjects, err = FindAllProjectsDependantOnImpactedProjects(impactedProjects, dependencyGraph)
impactedProjects, err = FindAllProjectsDependantOnImpactedProjects(impactedProjects, dependencyGraph, changedFiles)
if err != nil {
return &ProcessIssueCommentEventResult{}, fmt.Errorf("failed to find all projects dependant on impacted projects")
}
Expand All @@ -52,7 +52,7 @@ func ProcessIssueCommentEvent(prNumber int, diggerConfig *digger_config.DiggerCo

}

func FindAllProjectsDependantOnImpactedProjects(impactedProjects []digger_config.Project, dependencyGraph graph.Graph[string, digger_config.Project]) ([]digger_config.Project, error) {
func FindAllProjectsDependantOnImpactedProjects(impactedProjects []digger_config.Project, dependencyGraph graph.Graph[string, digger_config.Project], changedFiles []string) ([]digger_config.Project, error) {
impactedProjectsMap := make(map[string]digger_config.Project)
for _, project := range impactedProjects {
impactedProjectsMap[project.Name] = project
Expand Down Expand Up @@ -82,7 +82,7 @@ func FindAllProjectsDependantOnImpactedProjects(impactedProjects []digger_config
} else {
// if a project was not impacted, check if it has a parent that was impacted and add it to the map of impacted projects
for parent := range predecessorMap[node] {
if _, ok := impactedProjectsMap[parent]; ok {
if _, ok := impactedProjectsMap[parent]; ok && shouldPropagateDependencyImpact(currentProject, parent, changedFiles) {
impactedProjectsWithDependantProjects = append(impactedProjectsWithDependantProjects, currentProject)
impactedProjectsMap[node] = currentProject
visited[node] = true
Expand All @@ -100,6 +100,26 @@ func FindAllProjectsDependantOnImpactedProjects(impactedProjects []digger_config
return impactedProjectsWithDependantProjects, nil
}

func shouldPropagateDependencyImpact(project digger_config.Project, dependencyProjectName string, changedFiles []string) bool {
patterns, ok := project.InferredDependencyPatternsByProject[dependencyProjectName]
if !ok {
return true
}

excludePatterns := digger_config.ResolvePatternsRelativeToProject(project.Dir, project.ExcludePatterns)
for _, changedFile := range changedFiles {
if digger_config.MatchIncludeExcludePatternsToFile(
changedFile,
append([]string(nil), patterns...),
append([]string(nil), excludePatterns...),
) {
return true
}
}

return false
}

func ConvertIssueCommentEventToJobs(repoFullName string, requestedBy string, prNumber int, commentBody string, impactedProjectsForComment []digger_config.Project, allImpactedProjects []digger_config.Project, workflows map[string]digger_config.Workflow, prBranchName string, defaultBranch string, performEnvVarInterpolation bool) ([]scheduler.Job, bool, error) {
jobs := make([]scheduler.Job, 0)
prBranch := prBranchName
Expand Down
11 changes: 10 additions & 1 deletion libs/ci/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -1154,7 +1154,16 @@ func ProcessGitHubPullRequestEvent(payload *github.PullRequestEvent, diggerConfi
slog.Debug("using hard dependency mode, finding all dependent projects", "prNumber", prNumber)
originalCount := len(impactedProjects)

impactedProjects, err = generic.FindAllProjectsDependantOnImpactedProjects(impactedProjects, dependencyGraph)
targetBranchDependencyGraph, err := generic.CreateTargetBranchDependencyGraph(diggerConfig.Projects, defaultBranch, targetBranch)
if err != nil {
slog.Error("failed to create target branch dependency graph",
"error", err,
"prNumber", prNumber,
"targetBranch", targetBranch)
return nil, nil, prNumber, fmt.Errorf("failed to create target branch dependency graph")
}

impactedProjects, err = generic.FindAllProjectsDependantOnImpactedProjects(impactedProjects, targetBranchDependencyGraph, changedFiles)
if err != nil {
slog.Error("failed to find all projects dependant on impacted projects",
"error", err,
Expand Down
Loading