Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Assign SCP Stacks to OUs/Accounts #28

Merged
merged 11 commits into from
Apr 4, 2024
33 changes: 2 additions & 31 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
package cmd

import (
"context"
"fmt"

"github.com/santiago-labs/telophasecli/cmd/runner"
"github.com/santiago-labs/telophasecli/lib/awsorgs"
"github.com/santiago-labs/telophasecli/lib/ymlparser"
"github.com/santiago-labs/telophasecli/resource"
"github.com/santiago-labs/telophasecli/resourceoperation"

"github.com/spf13/cobra"
Expand All @@ -33,8 +27,6 @@ var compileCmd = &cobra.Command{
Use: "deploy",
Short: "deploy - Deploy a CDK and/or TF stacks to your AWS account(s). Accounts in organization.yml will be created if they do not exist.",
Run: func(cmd *cobra.Command, args []string) {
orgClient := awsorgs.New()
ctx := context.Background()

var consoleUI runner.ConsoleUI
if useTUI {
Expand All @@ -43,29 +35,8 @@ var compileCmd = &cobra.Command{
consoleUI = runner.NewSTDOut()
}

var accountsToApply []resource.Account
rootAWSGroup, err := ymlparser.ParseOrganizationV2(orgFile)
if err != nil {
panic(fmt.Sprintf("error: %s", err))
}

ops := orgV2Diff(ctx, consoleUI, orgClient, rootAWSGroup, resourceoperation.Deploy)
for _, op := range ops {
op.Call(ctx)
}

if rootAWSGroup != nil {
for _, acct := range rootAWSGroup.AllDescendentAccounts() {
if contains(tag, acct.AllTags()) || tag == "" {
accountsToApply = append(accountsToApply, *acct)
}
}
}

if len(accountsToApply) == 0 {
fmt.Println("No accounts to deploy")
}
go processOrgEndToEnd(consoleUI, resourceoperation.Deploy)
consoleUI.Start()

runIAC(ctx, consoleUI, resourceoperation.Deploy, accountsToApply)
},
}
32 changes: 2 additions & 30 deletions cmd/diff.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
package cmd

import (
"context"
"fmt"

"github.com/santiago-labs/telophasecli/cmd/runner"
"github.com/santiago-labs/telophasecli/lib/awsorgs"
"github.com/santiago-labs/telophasecli/lib/ymlparser"
"github.com/santiago-labs/telophasecli/resource"
"github.com/santiago-labs/telophasecli/resourceoperation"

"github.com/spf13/cobra"
Expand All @@ -25,7 +19,6 @@ var diffCmd = &cobra.Command{
Use: "diff",
Short: "diff - Show accounts to create/update and CDK and/or TF changes for each account.",
Run: func(cmd *cobra.Command, args []string) {
orgClient := awsorgs.New()

var consoleUI runner.ConsoleUI
if useTUI {
Expand All @@ -34,28 +27,7 @@ var diffCmd = &cobra.Command{
consoleUI = runner.NewSTDOut()
}

var accountsToApply []resource.Account

ctx := context.Background()

rootAWSGroup, err := ymlparser.ParseOrganizationV2(orgFile)
if err != nil {
panic(fmt.Sprintf("error: %s", err))
}
orgV2Diff(ctx, consoleUI, orgClient, rootAWSGroup, resourceoperation.Diff)

if rootAWSGroup != nil {
for _, acct := range rootAWSGroup.AllDescendentAccounts() {
if contains(tag, acct.AllTags()) || tag == "" {
accountsToApply = append(accountsToApply, *acct)
}
}
}

if len(accountsToApply) == 0 {
fmt.Println("No accounts to deploy")
}

runIAC(ctx, consoleUI, resourceoperation.Diff, accountsToApply)
go processOrgEndToEnd(consoleUI, resourceoperation.Diff)
consoleUI.Start()
},
}
5 changes: 3 additions & 2 deletions cmd/iac.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,13 @@ func runIAC(
}

for _, op := range ops {
op.Call(ctx)
if err := op.Call(ctx); err != nil {
panic(err)
}
}
}(accts[i])
}

consoleUI.PostProcess()
wg.Wait()
}
func contains(e string, s []string) bool {
Expand Down
88 changes: 42 additions & 46 deletions cmd/provisionaccounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,10 @@ import (
"errors"
"fmt"

"github.com/aws/aws-sdk-go/aws/session"

"github.com/aws/aws-sdk-go/service/sts"
"github.com/spf13/cobra"

"github.com/santiago-labs/telophasecli/cmd/runner"
"github.com/santiago-labs/telophasecli/lib/awsorgs"
"github.com/santiago-labs/telophasecli/lib/awssess"
"github.com/santiago-labs/telophasecli/lib/ymlparser"
"github.com/santiago-labs/telophasecli/resource"
"github.com/santiago-labs/telophasecli/resourceoperation"
Expand All @@ -23,6 +19,7 @@ var orgFile string
func init() {
rootCmd.AddCommand(accountProvision)
accountProvision.Flags().StringVar(&orgFile, "org", "organization.yml", "Path to the organization.yml file")
accountProvision.Flags().BoolVar(&useTUI, "tui", false, "use the TUI for diff")
}

func isValidAccountArg(arg string) bool {
Expand Down Expand Up @@ -51,7 +48,6 @@ var accountProvision = &cobra.Command{
return fmt.Errorf("invalid color specified: %s", args[0])
},
Run: func(cmd *cobra.Command, args []string) {
orgClient := awsorgs.New()

var consoleUI runner.ConsoleUI
if useTUI {
Expand All @@ -60,48 +56,61 @@ var accountProvision = &cobra.Command{
consoleUI = runner.NewSTDOut()
}

ctx := context.Background()
if args[0] == "import" {
if err := importOrgV2(orgClient); err != nil {
panic(fmt.Sprintf("error importing organization: %s", err))
}
}
go processOrg(consoleUI, args[0])
consoleUI.Start()

rootAWSGroup, err := ymlparser.ParseOrganizationV2(orgFile)
if err != nil {
panic(fmt.Sprintf("error: %s", err))
}
if args[0] == "diff" {
orgV2Diff(ctx, consoleUI, orgClient, rootAWSGroup, resourceoperation.Diff)
},
}

func processOrg(consoleUI runner.ConsoleUI, cmd string) {
orgClient := awsorgs.New()
ctx := context.Background()
mgmtAcct, err := orgClient.FetchManagementAccount(ctx)
if err != nil {
panic(err)
}
if cmd == "import" {
consoleUI.Print("Importing AWS Organization", *mgmtAcct)
if err := importOrgV2(ctx, consoleUI, orgClient, mgmtAcct); err != nil {
consoleUI.Print(fmt.Sprintf("error importing organization: %s", err), *mgmtAcct)
}
}

rootAWSGroup, err := ymlparser.ParseOrganizationV2(orgFile)
if err != nil {
consoleUI.Print(fmt.Sprintf("error parsing organization: %s", err), *mgmtAcct)
}
if cmd == "diff" {
consoleUI.Print("Diffing AWS Organization", *mgmtAcct)
orgV2Diff(ctx, consoleUI, orgClient, rootAWSGroup, mgmtAcct, resourceoperation.Diff)
}

if args[0] == "deploy" {
operations := orgV2Diff(ctx, consoleUI, orgClient, rootAWSGroup, resourceoperation.Deploy)
if cmd == "deploy" {
consoleUI.Print("Diffing AWS Organization", *mgmtAcct)
operations := orgV2Diff(ctx, consoleUI, orgClient, rootAWSGroup, mgmtAcct, resourceoperation.Deploy)

for _, op := range operations {
err := op.Call(ctx)
if err != nil {
panic(fmt.Sprintf("error: %s", err))
}
for _, op := range operations {
err := op.Call(ctx)
if err != nil {
panic(fmt.Sprintf("error: %s", err))
}
}
},
}

consoleUI.Print("Done.", *mgmtAcct)
}

func orgV2Diff(
ctx context.Context,
outputUI runner.ConsoleUI,
orgClient awsorgs.Client,
rootAWSGroup *resource.AccountGroup,
mgmtAcct *resource.Account,
operation int,
) []resourceoperation.ResourceOperation {

var operations []resourceoperation.ResourceOperation
if rootAWSGroup != nil {
mgmtAcct, err := orgClient.FetchManagementAccount(ctx)
if err != nil {
panic(err)
}
operations = append(operations, resourceoperation.CollectOrganizationUnitOps(
ctx, outputUI, orgClient, rootAWSGroup, operation,
)...)
Expand All @@ -116,21 +125,7 @@ func orgV2Diff(
return operations
}

func currentAccountID() (string, error) {
stsClient := sts.New(session.Must(awssess.DefaultSession()))
caller, err := stsClient.GetCallerIdentity(&sts.GetCallerIdentityInput{})
if err != nil {
return "", err
}

return *caller.Account, nil
}

func importOrgV2(orgClient awsorgs.Client) error {
managingAccountID, err := currentAccountID()
if err != nil {
return err
}
func importOrgV2(ctx context.Context, consoleUI runner.ConsoleUI, orgClient awsorgs.Client, mgmtAcct *resource.Account) error {

rootId, err := orgClient.GetRootId()
if err != nil {
Expand All @@ -140,12 +135,12 @@ func importOrgV2(orgClient awsorgs.Client) error {
return fmt.Errorf("no root ID found")
}

rootGroup, err := orgClient.FetchGroupAndDescendents(context.TODO(), rootId, managingAccountID)
rootGroup, err := orgClient.FetchGroupAndDescendents(ctx, rootId, mgmtAcct.AccountID)
if err != nil {
return err
}
org := resource.AccountGroup{
Name: rootGroup.Name,
GroupName: rootGroup.GroupName,
ChildGroups: rootGroup.ChildGroups,
Accounts: rootGroup.Accounts,
}
Expand All @@ -154,5 +149,6 @@ func importOrgV2(orgClient awsorgs.Client) error {
return err
}

consoleUI.Print(fmt.Sprintf("Successfully wrote file to: %s", orgFile), *mgmtAcct)
return nil
}
48 changes: 48 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package cmd

import (
"context"
"fmt"
"os"

"github.com/santiago-labs/telophasecli/cmd/runner"
"github.com/santiago-labs/telophasecli/lib/awsorgs"
"github.com/santiago-labs/telophasecli/lib/metrics"
"github.com/santiago-labs/telophasecli/lib/ymlparser"
"github.com/santiago-labs/telophasecli/resource"
"github.com/santiago-labs/telophasecli/resourceoperation"
"github.com/spf13/cobra"
)

Expand All @@ -27,3 +33,45 @@ func Execute() {
os.Exit(1)
}
}

func processOrgEndToEnd(consoleUI runner.ConsoleUI, cmd int) {
ctx := context.Background()
orgClient := awsorgs.New()
mgmtAcct, err := orgClient.FetchManagementAccount(ctx)
if err != nil {
panic(err)
}

var accountsToApply []resource.Account
rootAWSGroup, err := ymlparser.ParseOrganizationV2(orgFile)
if err != nil {
panic(fmt.Sprintf("error: %s", err))
}
orgV2Diff(ctx, consoleUI, orgClient, rootAWSGroup, mgmtAcct, cmd)

if rootAWSGroup != nil {
for _, acct := range rootAWSGroup.AllDescendentAccounts() {
if contains(tag, acct.AllTags()) || tag == "" {
accountsToApply = append(accountsToApply, *acct)
}
}
}

if len(accountsToApply) == 0 {
consoleUI.Print("No accounts to deploy.", *mgmtAcct)
}

runIAC(ctx, consoleUI, cmd, accountsToApply)

scpOps := resourceoperation.CollectSCPOps(ctx, orgClient, consoleUI, cmd, rootAWSGroup, mgmtAcct)
for _, op := range scpOps {
err := op.Call(ctx)
if err != nil {
consoleUI.Print(fmt.Sprintf("Error on SCP Operation: %v", err), *mgmtAcct)
}
}

if len(scpOps) == 0 {
consoleUI.Print("No Service Control Policies to deploy.", *mgmtAcct)
}
}
2 changes: 1 addition & 1 deletion cmd/runner/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ import (
type ConsoleUI interface {
Print(string, resource.Account)
RunCmd(*exec.Cmd, resource.Account) error
PostProcess()
Start()
}
4 changes: 2 additions & 2 deletions cmd/runner/stdout.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func (s *stdOut) RunCmd(cmd *exec.Cmd, acct resource.Account) error {

var scannerWg sync.WaitGroup
scannerWg.Add(2)
scanF := func(scanner *bufio.Scanner, name string) {
scanF := func(scanner *bufio.Scanner, _ string) {
defer scannerWg.Done()
for scanner.Scan() {
fmt.Printf("%s %s\n", s.ColoredId(acct), scanner.Text())
Expand All @@ -86,4 +86,4 @@ func (s *stdOut) Print(msg string, acct resource.Account) {
fmt.Printf("%s %v\n", s.ColoredId(acct), msg)
}

func (s *stdOut) PostProcess() {}
func (s *stdOut) Start() {}
Loading
Loading