Skip to content

Commit 87bc331

Browse files
committed
cmd: exit 1 if cdk, cloudformation or terraform fail
If the diff or deploy fail then we want to ensure that telophase exits with a non-zero status code. This makes it easier to use in a script with set -e to show up as red when telophasecli is used in CI
1 parent d36a5fc commit 87bc331

File tree

6 files changed

+66
-15
lines changed

6 files changed

+66
-15
lines changed

cmd/deploy.go

+17-5
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ package cmd
22

33
import (
44
"fmt"
5+
"log"
6+
"os"
57
"strings"
68

79
"github.com/santiago-labs/telophasecli/cmd/runner"
810
"github.com/santiago-labs/telophasecli/resourceoperation"
11+
"golang.org/x/sync/errgroup"
912

1013
"github.com/spf13/cobra"
1114
)
@@ -34,20 +37,29 @@ var deployCmd = &cobra.Command{
3437
Run: func(cmd *cobra.Command, args []string) {
3538

3639
if err := validateTargets(); err != nil {
37-
fmt.Println(err)
38-
return
40+
log.Fatal("error validating targets err:", err)
3941
}
4042
var consoleUI runner.ConsoleUI
4143
parsedTargets := filterEmptyStrings(strings.Split(targets, ","))
44+
var g errgroup.Group
45+
4246
if useTUI {
4347
consoleUI = runner.NewTUI()
44-
go ProcessOrgEndToEnd(consoleUI, resourceoperation.Deploy, parsedTargets)
48+
g.Go(func() error {
49+
return ProcessOrgEndToEnd(consoleUI, resourceoperation.Deploy, parsedTargets)
50+
})
4551
} else {
4652
consoleUI = runner.NewSTDOut()
47-
ProcessOrgEndToEnd(consoleUI, resourceoperation.Deploy, parsedTargets)
53+
if err := ProcessOrgEndToEnd(consoleUI, resourceoperation.Deploy, parsedTargets); err != nil {
54+
fmt.Println(err)
55+
os.Exit(1)
56+
}
4857
}
4958

5059
consoleUI.Start()
51-
60+
if err := g.Wait(); err != nil {
61+
fmt.Println(err)
62+
os.Exit(1)
63+
}
5264
},
5365
}

cmd/diff.go

+16-4
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ package cmd
22

33
import (
44
"fmt"
5+
"log"
6+
"os"
57
"strings"
68

79
"github.com/santiago-labs/telophasecli/cmd/runner"
810
"github.com/santiago-labs/telophasecli/resourceoperation"
11+
"golang.org/x/sync/errgroup"
912

1013
"github.com/spf13/cobra"
1114
)
@@ -25,20 +28,29 @@ var diffCmd = &cobra.Command{
2528
Run: func(cmd *cobra.Command, args []string) {
2629

2730
if err := validateTargets(); err != nil {
28-
fmt.Println(err)
29-
return
31+
log.Fatal("error validating targets err:", err)
3032
}
3133
var consoleUI runner.ConsoleUI
3234
parsedTargets := filterEmptyStrings(strings.Split(targets, ","))
3335

36+
var g errgroup.Group
37+
3438
if useTUI {
3539
consoleUI = runner.NewTUI()
36-
go ProcessOrgEndToEnd(consoleUI, resourceoperation.Diff, parsedTargets)
40+
g.Go(func() error {
41+
return ProcessOrgEndToEnd(consoleUI, resourceoperation.Diff, parsedTargets)
42+
})
3743
} else {
3844
consoleUI = runner.NewSTDOut()
39-
ProcessOrgEndToEnd(consoleUI, resourceoperation.Diff, parsedTargets)
45+
if err := ProcessOrgEndToEnd(consoleUI, resourceoperation.Diff, parsedTargets); err != nil {
46+
fmt.Println(err)
47+
os.Exit(1)
48+
}
4049
}
4150

4251
consoleUI.Start()
52+
if err := g.Wait(); err != nil {
53+
os.Exit(1)
54+
}
4355
},
4456
}

cmd/iac.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@ func runIAC(
1616
consoleUI runner.ConsoleUI,
1717
cmd int,
1818
accts []resource.Account,
19-
) {
19+
) error {
2020
var wg sync.WaitGroup
2121

22+
var once sync.Once
23+
var retError error
24+
2225
for i := range accts {
2326
wg.Add(1)
2427
go func(acct resource.Account) {
@@ -40,6 +43,9 @@ func runIAC(
4043

4144
for _, op := range ops {
4245
if err := op.Call(ctx); err != nil {
46+
once.Do(func() {
47+
retError = err
48+
})
4349
consoleUI.Print(fmt.Sprintf("%v", err), acct)
4450
return
4551
}
@@ -48,6 +54,8 @@ func runIAC(
4854
}
4955

5056
wg.Wait()
57+
58+
return retError
5159
}
5260
func contains(e string, s []string) bool {
5361
for _, a := range s {

cmd/root.go

+22-5
Original file line numberDiff line numberDiff line change
@@ -36,24 +36,28 @@ func Execute() {
3636
}
3737
}
3838

39-
func ProcessOrgEndToEnd(consoleUI runner.ConsoleUI, cmd int, targets []string) {
39+
func setOpsError() error {
40+
return fmt.Errorf("error running operations")
41+
}
42+
43+
func ProcessOrgEndToEnd(consoleUI runner.ConsoleUI, cmd int, targets []string) error {
4044
ctx := context.Background()
4145
orgClient := awsorgs.New()
4246
rootAWSOU, err := ymlparser.NewParser(orgClient).ParseOrganization(ctx, orgFile)
4347
if err != nil {
4448
consoleUI.Print(fmt.Sprintf("error: %s", err), resource.Account{AccountID: "error", AccountName: "error"})
45-
return
49+
return oops.Wrapf(err, "ParseOrg")
4650
}
4751

4852
if rootAWSOU == nil {
4953
consoleUI.Print("Could not parse AWS Organization", resource.Account{AccountID: "error", AccountName: "error"})
50-
return
54+
return oops.Errorf("No root AWS OU")
5155
}
5256

5357
mgmtAcct, err := resolveMgmtAcct(ctx, orgClient, rootAWSOU)
5458
if err != nil {
5559
consoleUI.Print(fmt.Sprintf("Could not fetch AWS Management Account: %s", err), resource.Account{AccountID: "error", AccountName: "error"})
56-
return
60+
return oops.Wrapf(err, "resolveMgmtAcct")
5761
}
5862

5963
var deployStacks bool
@@ -72,6 +76,11 @@ func ProcessOrgEndToEnd(consoleUI runner.ConsoleUI, cmd int, targets []string) {
7276
}
7377
}
7478

79+
// opsError is the error we return eventually. We want to allow partially
80+
// applied operations across organizations, IaC, and SCPs so we only return
81+
// this error in the end.
82+
var opsError error
83+
7584
if len(targets) == 0 || deployOrganization {
7685
orgOps := resourceoperation.CollectOrganizationUnitOps(
7786
ctx, consoleUI, orgClient, mgmtAcct, rootAWSOU, cmd,
@@ -88,6 +97,7 @@ func ProcessOrgEndToEnd(consoleUI runner.ConsoleUI, cmd int, targets []string) {
8897
err := op.Call(ctx)
8998
if err != nil {
9099
consoleUI.Print(fmt.Sprintf("Error on AWS Organization Operation: %v", err), *mgmtAcct)
100+
opsError = setOpsError()
91101
}
92102
}
93103
}
@@ -105,7 +115,11 @@ func ProcessOrgEndToEnd(consoleUI runner.ConsoleUI, cmd int, targets []string) {
105115
consoleUI.Print("No accounts to deploy.", *mgmtAcct)
106116
}
107117

108-
runIAC(ctx, consoleUI, cmd, accountsToApply)
118+
err := runIAC(ctx, consoleUI, cmd, accountsToApply)
119+
if err != nil {
120+
consoleUI.Print("No accounts to deploy.", *mgmtAcct)
121+
opsError = setOpsError()
122+
}
109123
}
110124

111125
if len(targets) == 0 || deploySCP {
@@ -124,14 +138,17 @@ func ProcessOrgEndToEnd(consoleUI runner.ConsoleUI, cmd int, targets []string) {
124138
err := op.Call(ctx)
125139
if err != nil {
126140
consoleUI.Print(fmt.Sprintf("Error on SCP Operation: %v", err), *scpAdmin)
141+
opsError = setOpsError()
127142
}
128143
}
129144

130145
if len(scpOps) == 0 {
131146
consoleUI.Print("No Service Control Policies to deploy.", *scpAdmin)
132147
}
133148
}
149+
134150
consoleUI.Print("Done.\n", *mgmtAcct)
151+
return opsError
135152
}
136153

137154
func validateTargets() error {

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ require (
1313
github.com/samsarahq/go/oops v0.0.0-20220211150445-4b291d6feac4
1414
github.com/spf13/cobra v1.7.0
1515
github.com/stretchr/testify v1.8.4
16+
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
1617
gopkg.in/yaml.v3 v3.0.1
1718
)
1819

go.sum

+1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
6666
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
6767
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
6868
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
69+
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
6970
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
7071
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
7172
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

0 commit comments

Comments
 (0)