Skip to content

Commit cb095d2

Browse files
authored
Support workspaces (#72)
* support workspaces
1 parent 5e384c8 commit cb095d2

File tree

5 files changed

+120
-28
lines changed

5 files changed

+120
-28
lines changed

pkg/digger/digger.go

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"digger/pkg/terraform"
77
"digger/pkg/utils"
88
"encoding/json"
9+
"errors"
910
"fmt"
1011
"log"
1112
"os"
@@ -58,6 +59,7 @@ func RunCommandsPerProject(commandsPerProject []ProjectCommand, repoOwner string
5859
}
5960
diggerExecutor := DiggerExecutor{
6061
workingDir,
62+
projectCommands.ProjectWorkspace,
6163
repoOwner,
6264
projectCommands.ProjectName,
6365
projectCommands.ProjectDir,
@@ -99,9 +101,10 @@ func GetGitHubContext(ghContext string) (*models.Github, error) {
99101
}
100102

101103
type ProjectCommand struct {
102-
ProjectName string
103-
ProjectDir string
104-
Commands []string
104+
ProjectName string
105+
ProjectDir string
106+
ProjectWorkspace string
107+
Commands []string
105108
}
106109

107110
func ConvertGithubEventToCommands(event models.Event, impactedProjects []Project) ([]ProjectCommand, error) {
@@ -113,21 +116,24 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []Project
113116
for _, project := range impactedProjects {
114117
if event.Action == "closed" && event.PullRequest.Merged && event.PullRequest.Base.Ref == event.Repository.DefaultBranch {
115118
commandsPerProject = append(commandsPerProject, ProjectCommand{
116-
ProjectName: project.Name,
117-
ProjectDir: project.Dir,
118-
Commands: project.WorkflowConfiguration.OnCommitToDefault,
119+
ProjectName: project.Name,
120+
ProjectDir: project.Dir,
121+
ProjectWorkspace: project.Workspace,
122+
Commands: project.WorkflowConfiguration.OnCommitToDefault,
119123
})
120124
} else if event.Action == "opened" || event.Action == "reopened" || event.Action == "synchronize" {
121125
commandsPerProject = append(commandsPerProject, ProjectCommand{
122-
ProjectName: project.Name,
123-
ProjectDir: project.Dir,
124-
Commands: project.WorkflowConfiguration.OnPullRequestPushed,
126+
ProjectName: project.Name,
127+
ProjectDir: project.Dir,
128+
ProjectWorkspace: project.Workspace,
129+
Commands: project.WorkflowConfiguration.OnPullRequestPushed,
125130
})
126131
} else if event.Action == "closed" {
127132
commandsPerProject = append(commandsPerProject, ProjectCommand{
128-
ProjectName: project.Name,
129-
ProjectDir: project.Dir,
130-
Commands: project.WorkflowConfiguration.OnPullRequestClosed,
133+
ProjectName: project.Name,
134+
ProjectDir: project.Dir,
135+
ProjectWorkspace: project.Workspace,
136+
Commands: project.WorkflowConfiguration.OnPullRequestClosed,
131137
})
132138
}
133139
}
@@ -139,10 +145,19 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []Project
139145
for _, command := range supportedCommands {
140146
if strings.Contains(event.Comment.Body, command) {
141147
for _, project := range impactedProjects {
148+
workspace := project.Workspace
149+
workspaceOverride, err := parseWorkspace(event.Comment.Body)
150+
if err != nil {
151+
return []ProjectCommand{}, err
152+
}
153+
if workspaceOverride != "" {
154+
workspace = workspaceOverride
155+
}
142156
commandsPerProject = append(commandsPerProject, ProjectCommand{
143-
ProjectName: project.Name,
144-
ProjectDir: project.Dir,
145-
Commands: []string{command},
157+
ProjectName: project.Name,
158+
ProjectDir: project.Dir,
159+
ProjectWorkspace: workspace,
160+
Commands: []string{command},
146161
})
147162
}
148163
}
@@ -153,6 +168,25 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []Project
153168
}
154169
}
155170

171+
func parseWorkspace(comment string) (string, error) {
172+
re := regexp.MustCompile(`-w(?:\s+(\S+)|$)`)
173+
matches := re.FindAllStringSubmatch(comment, -1)
174+
175+
if len(matches) == 0 {
176+
return "", nil
177+
}
178+
179+
if len(matches) > 1 {
180+
return "", errors.New("more than one -w flag found")
181+
}
182+
183+
if len(matches[0]) < 2 || matches[0][1] == "" {
184+
return "", errors.New("no value found after -w flag")
185+
}
186+
187+
return matches[0][1], nil
188+
}
189+
156190
func parseProjectName(comment string) string {
157191
re := regexp.MustCompile(`-p ([a-zA-Z\-]+)`)
158192
match := re.FindStringSubmatch(comment)
@@ -164,6 +198,7 @@ func parseProjectName(comment string) string {
164198

165199
type DiggerExecutor struct {
166200
workingDir string
201+
workspace string
167202
repoOwner string
168203
projectName string
169204
projectDir string
@@ -179,7 +214,7 @@ func (d DiggerExecutor) LockId() string {
179214

180215
func (d DiggerExecutor) Plan(prNumber int) {
181216

182-
terraformExecutor := terraform.Terraform{WorkingDir: path.Join(d.workingDir, d.projectDir)}
217+
terraformExecutor := terraform.Terraform{WorkingDir: path.Join(d.workingDir, d.projectDir), Workspace: d.workspace}
183218

184219
res, err := d.lock.Lock(d.LockId(), prNumber)
185220
if err != nil {
@@ -199,7 +234,7 @@ func (d DiggerExecutor) Plan(prNumber int) {
199234
}
200235

201236
func (d DiggerExecutor) Apply(prNumber int) {
202-
terraformExecutor := terraform.Terraform{WorkingDir: path.Join(d.workingDir, d.projectDir)}
237+
terraformExecutor := terraform.Terraform{WorkingDir: path.Join(d.workingDir, d.projectDir), Workspace: d.workspace}
203238
if res, _ := d.lock.Lock(d.LockId(), prNumber); res {
204239
stdout, stderr, err := terraformExecutor.Apply()
205240
applyOutput := cleanupTerraformApply(true, err, stdout, stderr)

pkg/digger/digger_config.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,16 @@ type DiggerConfig struct {
2424
type Project struct {
2525
Name string `yaml:"name"`
2626
Dir string `yaml:"dir"`
27+
Workspace string `yaml:"workspace"`
2728
WorkflowConfiguration WorkflowConfiguration `yaml:"workflow_configuration"`
2829
}
2930

30-
var ErrDiggerConfigConflict error = errors.New("more than one digger config file detected, please keep either 'digger.yml' or 'digger.yaml'")
31+
var ErrDiggerConfigConflict = errors.New("more than one digger config file detected, please keep either 'digger.yml' or 'digger.yaml'")
3132

3233
func (p *Project) UnmarshalYAML(unmarshal func(interface{}) error) error {
3334
type rawProject Project
3435
raw := rawProject{
36+
Workspace: "default",
3537
WorkflowConfiguration: WorkflowConfiguration{
3638
OnPullRequestPushed: []string{"digger plan"},
3739
OnPullRequestClosed: []string{"digger unlock"},
@@ -58,11 +60,7 @@ func NewDiggerConfig(workingDir string) (*DiggerConfig, error) {
5860
data, err := os.ReadFile(fileName)
5961
if err != nil {
6062
config.Projects = make([]Project, 1)
61-
config.Projects[0] = Project{Name: "default", Dir: ".", WorkflowConfiguration: WorkflowConfiguration{
62-
OnPullRequestPushed: []string{"digger plan"},
63-
OnPullRequestClosed: []string{"digger unlock"},
64-
OnCommitToDefault: []string{"digger apply"},
65-
}}
63+
config.Projects[0] = defaultProject()
6664
return config, nil
6765
}
6866

@@ -73,6 +71,18 @@ func NewDiggerConfig(workingDir string) (*DiggerConfig, error) {
7371
return config, nil
7472
}
7573

74+
func defaultProject() Project {
75+
return Project{
76+
Name: "default",
77+
Dir: ".",
78+
Workspace: "default",
79+
WorkflowConfiguration: WorkflowConfiguration{
80+
OnPullRequestPushed: []string{"digger plan"},
81+
OnPullRequestClosed: []string{"digger unlock"},
82+
OnCommitToDefault: []string{"digger apply"},
83+
}}
84+
}
85+
7686
func (c *DiggerConfig) GetProject(projectName string) *Project {
7787
for _, project := range c.Projects {
7888
if projectName == project.Name {

pkg/digger/digger_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package digger
2+
3+
import "testing"
4+
5+
func TestParseWorkspace(t *testing.T) {
6+
var commentTests = []struct {
7+
in string
8+
out string
9+
err bool
10+
}{
11+
{"test", "", false},
12+
{"test -w workspace", "workspace", false},
13+
{"test -w workspace -w workspace2", "", true},
14+
{"test -w", "", true},
15+
}
16+
17+
for _, tt := range commentTests {
18+
out, err := parseWorkspace(tt.in)
19+
if tt.err {
20+
if err == nil {
21+
t.Errorf("parseWorkspace(%q) = %q, want error", tt.in, out)
22+
}
23+
} else {
24+
if err != nil {
25+
t.Errorf("parseWorkspace(%q) = %q, want %q", tt.in, err, tt.out)
26+
}
27+
if out != tt.out {
28+
t.Errorf("parseWorkspace(%q) = %q, want %q", tt.in, out, tt.out)
29+
}
30+
}
31+
}
32+
33+
}

pkg/terraform/tf.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"github.com/hashicorp/terraform-exec/tfexec"
7+
"log"
78
"os"
89
)
910

@@ -14,6 +15,7 @@ type TerraformExecutor interface {
1415

1516
type Terraform struct {
1617
WorkingDir string
18+
Workspace string
1719
}
1820

1921
func (terraform *Terraform) Apply() (string, string, error) {
@@ -36,6 +38,13 @@ func (terraform *Terraform) Apply() (string, string, error) {
3638
return stdout.GetString(), "", fmt.Errorf("terraform init failed. %s", err)
3739
}
3840

41+
err = tf.WorkspaceSelect(context.Background(), terraform.Workspace)
42+
43+
if err != nil {
44+
log.Printf("terraform workspace select failed. workspace: %v. dir: %v", terraform.Workspace, terraform.WorkingDir)
45+
return stdout.GetString(), "", fmt.Errorf("terraform select failed. %s", err)
46+
}
47+
3948
err = tf.Apply(context.Background())
4049
if err != nil {
4150
println("terraform plan failed.")
@@ -83,12 +92,17 @@ func (terraform *Terraform) Plan() (bool, string, string, error) {
8392
println("terraform init failed.")
8493
return false, stdout.GetString(), stderr.GetString(), fmt.Errorf("terraform init failed. %s", err)
8594
}
95+
err = tf.WorkspaceSelect(context.Background(), terraform.Workspace)
8696

87-
nonEmptyPlan, err := tf.Plan(context.Background())
97+
if err != nil {
98+
log.Printf("terraform workspace select failed. workspace: %v. dir: %v", terraform.Workspace, terraform.WorkingDir)
99+
return false, stdout.GetString(), stderr.GetString(), fmt.Errorf("terraform select failed. %s", err)
100+
}
101+
isNonEmptyPlan, err := tf.Plan(context.Background())
88102
if err != nil {
89103
println("terraform plan failed. dir: " + terraform.WorkingDir)
90-
return nonEmptyPlan, stdout.GetString(), stderr.GetString(), fmt.Errorf("terraform plan failed. %s", err)
104+
return isNonEmptyPlan, stdout.GetString(), stderr.GetString(), fmt.Errorf("terraform plan failed. %s", err)
91105
}
92106

93-
return nonEmptyPlan, stdout.GetString(), stderr.GetString(), nil
107+
return isNonEmptyPlan, stdout.GetString(), stderr.GetString(), nil
94108
}

pkg/terraform/tf_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ func TestExecuteTerraformPlan(t *testing.T) {
1818

1919
CreateValidTerraformTestFile(dir)
2020

21-
tf := Terraform{WorkingDir: dir}
21+
tf := Terraform{WorkingDir: dir, Workspace: "default"}
2222
_, _, _, err := tf.Plan()
2323
assert.NoError(t, err)
2424
}
@@ -34,7 +34,7 @@ func TestExecuteTerraformApply(t *testing.T) {
3434

3535
CreateValidTerraformTestFile(dir)
3636

37-
tf := Terraform{WorkingDir: dir}
37+
tf := Terraform{WorkingDir: dir, Workspace: "default"}
3838
_, _, err := tf.Apply()
3939
assert.NoError(t, err)
4040
}

0 commit comments

Comments
 (0)