commands/cleanup.go (153 lines of code) (raw):
package commands
import (
"flag"
"fmt"
"log"
"os"
"path"
"path/filepath"
"strings"
"time"
"github.com/azure/armstrong/report"
"github.com/azure/armstrong/tf"
"github.com/azure/armstrong/types"
"github.com/ms-henglu/pal/trace"
"github.com/sirupsen/logrus"
)
type CleanupCommand struct {
verbose bool
workingDir string
}
func (c *CleanupCommand) flags() *flag.FlagSet {
fs := defaultFlagSet("cleanup")
fs.BoolVar(&c.verbose, "v", false, "whether show terraform logs")
fs.StringVar(&c.workingDir, "working-dir", "", "path to Terraform configuration files")
fs.Usage = func() { logrus.Error(c.Help()) }
return fs
}
func (c CleanupCommand) Help() string {
helpText := `
Usage: armstrong cleanup [-v] [-working-dir <path to Terraform configuration files>]
` + c.Synopsis() + "\n\n" + helpForFlags(c.flags())
return strings.TrimSpace(helpText)
}
func (c CleanupCommand) Synopsis() string {
return "Clean up dependencies and testing resource"
}
func (c CleanupCommand) Run(args []string) int {
f := c.flags()
if err := f.Parse(args); err != nil {
logrus.Error(fmt.Sprintf("Error parsing command-line flags: %s", err))
return 1
}
if c.verbose {
log.SetOutput(os.Stdout)
logrus.SetLevel(logrus.DebugLevel)
logrus.Infof("verbose mode enabled")
}
return c.Execute()
}
func (c CleanupCommand) Execute() int {
const (
allPassedReportFileName = "Onboard Terraform - cleanup_all_passed_report.md"
partialPassedReportFileName = "Onboard Terraform - cleanup_partial_passed_report.md"
)
logrus.Infof("cleaning up resources...")
wd, err := os.Getwd()
if err != nil {
logrus.Error(fmt.Sprintf("failed to get working directory: %+v", err))
return 1
}
if c.workingDir != "" {
wd, err = filepath.Abs(c.workingDir)
if err != nil {
logrus.Error(fmt.Sprintf("working directory is invalid: %+v", err))
return 1
}
}
terraform, err := tf.NewTerraform(wd, c.verbose)
if err != nil {
logrus.Fatalf("creating terraform executable: %+v", err)
}
state, err := terraform.Show()
if err != nil {
logrus.Fatalf("failed to get terraform state: %+v", err)
}
passReport := tf.NewPassReportFromState(state)
idAddressMap := tf.NewIdAddressFromState(state)
reportDir := fmt.Sprintf("armstrong_cleanup_reports_%s", time.Now().Format(time.DateTime))
reportDir = strings.ReplaceAll(reportDir, ":", "-")
reportDir = strings.ReplaceAll(reportDir, " ", "_")
reportDir = path.Join(wd, reportDir)
err = os.Mkdir(reportDir, 0755)
if err != nil {
logrus.Fatalf("failed to create report directory: %+v", err)
}
logrus.Infof("running terraform init...")
_ = terraform.Init()
logrus.Infof("running terraform destroy...")
destroyErr := terraform.Destroy()
errorReport := types.ErrorReport{}
if destroyErr != nil {
logrus.Errorf("failed to destroy resources: %+v", destroyErr)
logs, err := trace.NewRequestTraceParser(trace.TextParser).ParseFromFile(path.Join(wd, "log.txt"))
if err != nil {
logrus.Errorf("failed to parse log.txt: %+v", err)
}
errorReport = tf.NewCleanupErrorReport(destroyErr, logs)
for i := range errorReport.Errors {
if address, ok := idAddressMap[errorReport.Errors[i].Id]; ok {
errorReport.Errors[i].Label = address
}
}
storeCleanupErrorReport(errorReport, reportDir)
resources := make([]types.Resource, 0)
if state, err := terraform.Show(); err == nil && state != nil && state.Values != nil && state.Values.RootModule != nil && state.Values.RootModule.Resources != nil {
for _, passRes := range passReport.Resources {
isDeleted := true
for _, res := range state.Values.RootModule.Resources {
if passRes.Address == res.Address {
isDeleted = false
break
}
}
if isDeleted {
resources = append(resources, passRes)
}
}
}
passReport.Resources = resources
storeCleanupReport(passReport, reportDir, partialPassedReportFileName)
} else {
logrus.Infof("all resources are cleaned up")
storeCleanupReport(passReport, reportDir, allPassedReportFileName)
}
logrus.Infof("---------------- Summary ----------------")
logrus.Infof("%d resources passed the cleanup tests.", len(passReport.Resources))
if len(errorReport.Errors) != 0 {
logrus.Infof("%d errors when cleanup the testing resources.", len(errorReport.Errors))
}
return 0
}
func storeCleanupReport(passReport types.PassReport, reportDir string, reportName string) {
if len(passReport.Resources) != 0 {
err := os.WriteFile(path.Join(reportDir, reportName), []byte(report.CleanupMarkdownReport(passReport)), 0644)
if err != nil {
logrus.Errorf("failed to save passed markdown report to %s: %+v", reportName, err)
} else {
logrus.Infof("markdown report saved to %s", reportName)
}
}
}
func storeCleanupErrorReport(errorReport types.ErrorReport, reportDir string) {
for _, r := range errorReport.Errors {
logrus.Warnf("found an error when deleting %s, address: %s\n", r.Type, r.Label)
markdownFilename := fmt.Sprintf("Error - %s_%s.md", strings.ReplaceAll(r.Type, "/", "_"), r.Label)
err := os.WriteFile(path.Join(reportDir, markdownFilename), []byte(report.CleanupErrorMarkdownReport(r, errorReport.Logs)), 0644)
if err != nil {
logrus.Errorf("failed to save markdown report to %s: %+v", markdownFilename, err)
} else {
logrus.Infof("markdown report saved to %s", markdownFilename)
}
}
}