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

Drift detection #6

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
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: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
strategy:
fail-fast: true
matrix:
go: ['1.20.x']
go: ['1.21.x']

steps:
- name: Checkout
Expand All @@ -43,7 +43,7 @@ jobs:
run: go mod verify

- name: Lint
uses: golangci/golangci-lint-action@v2
uses: golangci/golangci-lint-action@v3
with:
version: latest

Expand Down
121 changes: 121 additions & 0 deletions cli/actions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package cli

import (
"fmt"
"os"
"time"

"github.com/fatih/color"
"github.com/rogerwelin/cfnctl/aws"
"github.com/rogerwelin/cfnctl/commands"
"github.com/rogerwelin/cfnctl/pkg/client"
"github.com/rogerwelin/cfnctl/utils"
)

func (p plan) run() error {
ctl, err := commands.CommandBuilder(p.templatePath, p.paramFile, false)
if err != nil {
return err
}

_, err = commands.Plan(ctl, true)

return err
}

func (v validate) run() error {
greenBold := color.New(color.Bold, color.FgHiGreen).SprintFunc()
err := commands.Validate(v.templatePath)
if err != nil {
return err
}
fmt.Printf("%s The configuration is valid.\n", greenBold("Success!"))
return nil
}

func (a apply) run() error {
ctl, err := commands.CommandBuilder(a.templatePath, a.paramFile, a.autoApprove)
if err != nil {
return err
}
err = commands.Apply(ctl)
return err
}

func (d destroy) run() error {
ctl, err := commands.CommandBuilder(d.templatePath, "", d.autoApprove)
if err != nil {
return err
}

err = commands.Destroy(ctl)
return err
}

func (v version) run() error {
err := commands.OutputVersion(v.version, os.Stdout)
return err
}

func (o output) run() error {
svc, err := aws.NewAWS()
if err != nil {
return err
}

ctl := client.New(
client.WithSvc(svc),
client.WithOutput(os.Stdout),
)

err = commands.Output(ctl)
return err
}

func (d drift) run() error {
svc, err := aws.NewAWS()
if err != nil {
return err
}
stackName := utils.TrimFileSuffix(d.templatePath)

ctl := client.New(
client.WithSvc(svc),
client.WithStackName(stackName),
client.WithOutput(os.Stdout),
)
// get drift id
id, err := ctl.StackDriftInit()

if err != nil {
return err
}

// poll for completion
ticker := time.NewTicker(1 * time.Second)
for range ticker.C {
status, err := ctl.GetDriftStatus(id)
if err != nil {
return err
}
if status == "DETECTION_COMPLETE" {
break
}
}

status, err := ctl.GetStackDriftInfo()
if err != nil {
return err
}

for _, item := range status {
if item.StackResourceDriftStatus != "IN_SYNC" {
err := utils.JsonDiff(*item.ExpectedProperties, *item.ActualProperties, ctl.Output)
if err != nil {
return err
}
}
}

return nil
}
57 changes: 34 additions & 23 deletions cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ import (
"github.com/urfave/cli/v2"
)

const (
VERSION = "0.1.0"
)

var (
version = "0.1.0"
cmds = []string{"apply", "destroy", "plan", "validate", "version", "output", "help"}
cmds = []string{"apply", "destroy", "plan", "validate", "version", "output", "help"}
)

// RunCLI runs a new instance of cfnctl
Expand All @@ -23,12 +26,12 @@ func RunCLI(args []string) {
app.HelpName = "cfnctl"
app.EnableBashCompletion = true
app.UsageText = "cfntl [global options] <subcommand> [args]"
app.Version = version
app.Version = VERSION
app.HideVersion = true
app.CommandNotFound = func(c *cli.Context, command string) {
res := didyoumean.NameSuggestion(command, cmds)
if res == "" {
fmt.Println("apa") // FIX
fmt.Println("") // FIX
} else {
fmt.Println("Cfnctl has no command named: " + command + ". Did you mean: " + res + "?")
fmt.Println("\nToo see all of Cfnctl's top-level commands, run\n\tcfnctl --help")
Expand Down Expand Up @@ -58,12 +61,12 @@ func RunCLI(args []string) {
},
},
Action: func(c *cli.Context) error {
apply := Apply{
TemplatePath: c.String("template-file"),
ParamFile: c.String("param-file"),
AutoApprove: c.Bool("auto-approve"),
apply := apply{
templatePath: c.String("template-file"),
paramFile: c.String("param-file"),
autoApprove: c.Bool("auto-approve"),
}
err := apply.Run()
err := apply.run()
return err
},
},
Expand All @@ -82,11 +85,19 @@ func RunCLI(args []string) {
},
},
Action: func(c *cli.Context) error {
plan := Plan{
TemplatePath: c.String("template-file"),
ParamFile: c.String("param-file"),
plan := plan{
templatePath: c.String("template-file"),
paramFile: c.String("param-file"),
}
drift := drift{
templatePath: c.String("template-file"),
}
err := plan.Run()
err := plan.run()
if err != nil {
return err
}

err = drift.run()
return err
},
},
Expand All @@ -106,20 +117,20 @@ func RunCLI(args []string) {
},
},
Action: func(c *cli.Context) error {
destroy := Destroy{
AutoApprove: c.Bool("auto-approve"),
TemplatePath: c.String("template-file"),
destroy := destroy{
autoApprove: c.Bool("auto-approve"),
templatePath: c.String("template-file"),
}
err := destroy.Run()
err := destroy.run()
return err
},
},
{
Name: "output",
Usage: "Show all exported output values of the selected account and region",
Action: func(c *cli.Context) error {
out := Output{}
err := out.Run()
out := output{}
err := out.run()
return err
},
},
Expand All @@ -134,17 +145,17 @@ func RunCLI(args []string) {
},
},
Action: func(c *cli.Context) error {
v := Validate{TemplatePath: c.String("template-file")}
err := v.Run()
v := validate{templatePath: c.String("template-file")}
err := v.run()
return err
},
},
{
Name: "version",
Usage: "Show the current Cfnctl version",
Action: func(c *cli.Context) error {
v := Version{Version: version}
err := v.Run()
v := version{version: VERSION}
err := v.run()
return err
},
},
Expand Down
111 changes: 17 additions & 94 deletions cli/types.go
Original file line number Diff line number Diff line change
@@ -1,108 +1,31 @@
package cli

import (
"fmt"
"os"

"github.com/fatih/color"
"github.com/rogerwelin/cfnctl/aws"
"github.com/rogerwelin/cfnctl/commands"
"github.com/rogerwelin/cfnctl/pkg/client"
)

type Validate struct {
TemplatePath string
}

type Plan struct {
TemplatePath string
ParamFile string
}

type Apply struct {
AutoApprove bool
TemplatePath string
ParamFile string
type validate struct {
templatePath string
}

type Destroy struct {
AutoApprove bool
TemplatePath string
type plan struct {
templatePath string
paramFile string
}

type Output struct{}

type Version struct {
Version string
type apply struct {
autoApprove bool
templatePath string
paramFile string
}

// Runner interface simplifies command interaction
type Runner interface {
Run() error
type destroy struct {
autoApprove bool
templatePath string
}

// Run executes the function receives command
func (p *Plan) Run() error {
ctl, err := commands.CommandBuilder(p.TemplatePath, p.ParamFile, false)
if err != nil {
return err
}

_, err = commands.Plan(ctl, true)

return err
}
type output struct{}

// Run executes the function receives command
func (v *Validate) Run() error {
greenBold := color.New(color.Bold, color.FgHiGreen).SprintFunc()
err := commands.Validate(v.TemplatePath)
if err != nil {
return err
}
fmt.Printf("%s The configuration is valid.\n", greenBold("Success!"))
return nil
type version struct {
version string
}

// Run executes the function receives command
func (a *Apply) Run() error {
ctl, err := commands.CommandBuilder(a.TemplatePath, a.ParamFile, a.AutoApprove)
if err != nil {
return err
}
err = commands.Apply(ctl)
return err
}

// Run executes the function receives command
func (d *Destroy) Run() error {
ctl, err := commands.CommandBuilder(d.TemplatePath, "", d.AutoApprove)
if err != nil {
return err
}

err = commands.Destroy(ctl)
return err
}

// Run executes the function receives command
func (v *Version) Run() error {
err := commands.OutputVersion(v.Version, os.Stdout)
return err
}

// Run executes the function receives command
func (o *Output) Run() error {
svc, err := aws.NewAWS()
if err != nil {
return err
}

ctl := client.New(
client.WithSvc(svc),
client.WithOutput(os.Stdout),
)

err = commands.Output(ctl)
return err
type drift struct {
templatePath string
}
2 changes: 2 additions & 0 deletions commands/testdata/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ Resources:
- arn:aws:iam::aws:policy/CloudWatchReadOnlyAccess
- arn:aws:iam::aws:policy/CloudWatchLogsFullAccess

# create iam role here

Bucket:
Type: AWS::S3::Bucket

Expand Down
Loading