Skip to content

Commit

Permalink
Fix: Close provider properly during abort (#26)
Browse files Browse the repository at this point in the history
* Fix: Close provider properly during abort

* Move list and print func to resource pkg

* Move util/ to awstools-lib
  • Loading branch information
jckuester authored Feb 15, 2021
1 parent a5728db commit be41715
Show file tree
Hide file tree
Showing 13 changed files with 256 additions and 529 deletions.
2 changes: 2 additions & 0 deletions aws/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package aws
import (
"context"
"fmt"
"time"

"github.com/apex/log"
"github.com/aws/aws-sdk-go-v2/aws/external"
Expand Down Expand Up @@ -711,6 +712,7 @@ func NewClient(configs ...external.Config) (*Client, error) {
log.WithFields(log.Fields{
"profile": profile,
"region": cfg.Region,
"time": time.Now().Format("04:05.000"),
}).Debugf("created new instance of AWS client")

return client, nil
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/fatih/color v1.9.0
github.com/gobwas/glob v0.2.3
github.com/gruntwork-io/terratest v0.23.0
github.com/jckuester/awstools-lib v0.0.0-20210215194522-14607cce3470
github.com/jckuester/terradozer v0.1.3
github.com/onsi/gomega v1.9.0
github.com/pkg/errors v0.9.1
Expand Down
25 changes: 25 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,30 @@ github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKe
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/jckuester/awstools-lib v0.0.0-20210212200752-307022362457 h1:xf/avUnf5zhBDJCD5u6/IgT1hceQElx1q9rHW1pcvpI=
github.com/jckuester/awstools-lib v0.0.0-20210212200752-307022362457/go.mod h1:71+ozVxBDbhfKZH5ybJVxWo+mO1SVfpGiLfwXU5qD/w=
github.com/jckuester/awstools-lib v0.0.0-20210213113828-144d01dfb776 h1:ZisH10yJl3L1AuRmkPNhOZj6clFZX86dMUslcbAwvCw=
github.com/jckuester/awstools-lib v0.0.0-20210213113828-144d01dfb776/go.mod h1:71+ozVxBDbhfKZH5ybJVxWo+mO1SVfpGiLfwXU5qD/w=
github.com/jckuester/awstools-lib v0.0.0-20210213120546-79923f22ec17 h1:1qkVmHqW4kHb2iQ1vF7MwuoAb/VueOCh1j1YEeXt658=
github.com/jckuester/awstools-lib v0.0.0-20210213120546-79923f22ec17/go.mod h1:71+ozVxBDbhfKZH5ybJVxWo+mO1SVfpGiLfwXU5qD/w=
github.com/jckuester/awstools-lib v0.0.0-20210213122015-b91b58160d27 h1:6k7/gnLiMTSaCUi+6RE3TKnuG4bNa/H9Jj63xr3LeZ4=
github.com/jckuester/awstools-lib v0.0.0-20210213122015-b91b58160d27/go.mod h1:71+ozVxBDbhfKZH5ybJVxWo+mO1SVfpGiLfwXU5qD/w=
github.com/jckuester/awstools-lib v0.0.0-20210213184508-58fef899e0e7 h1:rjlUOTJ5d/vZ2P+vyDrKND4xToMI9X0hOz+6hd36Ipk=
github.com/jckuester/awstools-lib v0.0.0-20210213184508-58fef899e0e7/go.mod h1:71+ozVxBDbhfKZH5ybJVxWo+mO1SVfpGiLfwXU5qD/w=
github.com/jckuester/awstools-lib v0.0.0-20210213201536-b3d8a93d67a0 h1:lDkRtsLz8Rs9OSj9ChYAbfrGfk+0tBDtMO1SVtEkDSA=
github.com/jckuester/awstools-lib v0.0.0-20210213201536-b3d8a93d67a0/go.mod h1:71+ozVxBDbhfKZH5ybJVxWo+mO1SVfpGiLfwXU5qD/w=
github.com/jckuester/awstools-lib v0.0.0-20210213202606-fb656cd226fd h1:cHLGcwvlMpNTN+tsNCRNdy289iypeSzLzmYyM/kMIZc=
github.com/jckuester/awstools-lib v0.0.0-20210213202606-fb656cd226fd/go.mod h1:71+ozVxBDbhfKZH5ybJVxWo+mO1SVfpGiLfwXU5qD/w=
github.com/jckuester/awstools-lib v0.0.0-20210213203113-a32463ae719a h1:6UYnZKnoFV3RUiN58F0Jliob5duwpgiR9Go5mLdnEk8=
github.com/jckuester/awstools-lib v0.0.0-20210213203113-a32463ae719a/go.mod h1:71+ozVxBDbhfKZH5ybJVxWo+mO1SVfpGiLfwXU5qD/w=
github.com/jckuester/awstools-lib v0.0.0-20210214123157-3a7301153e57 h1:TkDopPYfCb6R5EIfzPJi8cW55yRmBbI/HYDfiauezsU=
github.com/jckuester/awstools-lib v0.0.0-20210214123157-3a7301153e57/go.mod h1:71+ozVxBDbhfKZH5ybJVxWo+mO1SVfpGiLfwXU5qD/w=
github.com/jckuester/awstools-lib v0.0.0-20210214124804-d61556f035ab h1:WUR10Dj0XUMDtAFou/4LlBLHctLo5kR7q9BP9NvFlQk=
github.com/jckuester/awstools-lib v0.0.0-20210214124804-d61556f035ab/go.mod h1:71+ozVxBDbhfKZH5ybJVxWo+mO1SVfpGiLfwXU5qD/w=
github.com/jckuester/awstools-lib v0.0.0-20210214131601-bbe567b52f2b h1:jitMIosIgzDzr1J6yq6/PUlOUK5cbJs3WQoCClpe380=
github.com/jckuester/awstools-lib v0.0.0-20210214131601-bbe567b52f2b/go.mod h1:71+ozVxBDbhfKZH5ybJVxWo+mO1SVfpGiLfwXU5qD/w=
github.com/jckuester/awstools-lib v0.0.0-20210215194522-14607cce3470 h1:d//j4SZKiysxLC/1/hLZ/VdQGJBrQG6hKHl6v61loWA=
github.com/jckuester/awstools-lib v0.0.0-20210215194522-14607cce3470/go.mod h1:71+ozVxBDbhfKZH5ybJVxWo+mO1SVfpGiLfwXU5qD/w=
github.com/jckuester/terradozer v0.1.3 h1:xrRxr+L58QAVz5Kwq2fyWCNiK1NWOuKo8g5Q2664WZ4=
github.com/jckuester/terradozer v0.1.3/go.mod h1:ER3EJojZmO2u6lfcdgnmC+Nrg/TV2T2bacY5FZpqgks=
github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE=
Expand All @@ -723,6 +747,7 @@ github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/joyent/triton-go v0.0.0-20180313100802-d8f9c0314926/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA=
Expand Down
157 changes: 56 additions & 101 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
package main

import (
"context"
"errors"
"fmt"
"io/ioutil"
stdlog "log"
"os"
"os/signal"
"strings"
"text/tabwriter"
"time"

"github.com/jckuester/awsls/util"

"github.com/apex/log"
"github.com/apex/log/handlers/cli"
aws_ssmhelpers "github.com/disneystreaming/go-ssmhelpers/aws"
"github.com/fatih/color"
"github.com/jckuester/awsls/aws"
awsls "github.com/jckuester/awsls/aws"
"github.com/jckuester/awsls/internal"
"github.com/jckuester/awsls/resource"
resource "github.com/jckuester/awsls/resource"
"github.com/jckuester/awstools-lib/aws"
"github.com/jckuester/awstools-lib/terraform"
flag "github.com/spf13/pflag"
)

Expand Down Expand Up @@ -121,7 +123,7 @@ func mainExitCode() int {
profiles = profilesFromConfig
}

clients, err := util.NewAWSClientPool(profiles, regions)
clients, err := aws.NewClientPool(profiles, regions)
if err != nil {
fmt.Fprint(os.Stderr, color.RedString("\nError: %s\n", err))

Expand All @@ -131,132 +133,85 @@ func mainExitCode() int {
// suppress provider debug and info logs
log.SetLevel(log.ErrorLevel)

clientKeys := make([]util.AWSClientKey, 0, len(clients))
clientKeys := make([]aws.ClientKey, 0, len(clients))
for k := range clients {
clientKeys = append(clientKeys, k)
}

ctx := context.Background()

// trap Ctrl+C and call cancel on the context
ctx, cancel := context.WithCancel(ctx)
signalCh := make(chan os.Signal, 1)
signal.Notify(signalCh, ignoreSignals...)
signal.Notify(signalCh, forwardSignals...)
defer func() {
signal.Stop(signalCh)
cancel()
}()
go func() {
select {
case <-signalCh:
fmt.Fprint(os.Stderr, color.RedString("\nAborting...\n"))
cancel()
case <-ctx.Done():
}
}()

if logDebug {
log.SetLevel(log.DebugLevel)
}

// initialize a Terraform AWS provider for each AWS client with a matching config
providers, err := util.NewProviderPool(clientKeys, "v3.16.0", "~/.awsls", 10*time.Second)
providers, err := terraform.NewProviderPool(ctx, clientKeys, "v3.16.0", "~/.awsls", 10*time.Second)
if err != nil {
fmt.Fprint(os.Stderr, color.RedString("\nError: %s\n", err))

if !errors.Is(err, context.Canceled) {
fmt.Fprint(os.Stderr, color.RedString("\nError: %s\n", err))
}
return 1
}

defer func() {
for _, p := range providers {
_ = p.Close()
}
}()

if logDebug {
log.SetLevel(log.DebugLevel)
}

for _, rType := range matchedTypes {
var resources []aws.Resource
var hasAttrs map[string]bool

for key, client := range clients {
err := client.SetAccountID()
if err != nil {
fmt.Fprint(os.Stderr, color.RedString("Error %s: %s\n", rType, err))
// any provider here is sufficient to check if a resource type has attributes
p := providers[clientKeys[0]]

return 1
}

res, err := aws.ListResourcesByType(&client, rType)
if err != nil {
fmt.Fprint(os.Stderr, color.RedString("Error %s: %s\n", rType, err))

continue
}

provider := providers[key]
hasAttrs, err := resource.HasAttributes(attributes, rType, &p)
if err != nil {
fmt.Fprint(os.Stderr, color.RedString("Error: failed to check if resource type has attribute: "+
"%s\n", err))
return 1
}

hasAttrs, err = resource.HasAttributes(attributes, rType, &provider)
if err != nil {
fmt.Fprint(os.Stderr, color.RedString("Error: failed to check if resource type has attribute: "+
"%s\n", err))
var resources []awsls.Resource

continue
}
resourcesCh := make(chan resource.UpdatedResources, 1)
go func() { resourcesCh <- resource.ListInMultipleAccountsAndRegions(rType, hasAttrs, clients, providers) }()
select {
case <-ctx.Done():
return 1
case result := <-resourcesCh:
resources = result.Resources

if len(hasAttrs) > 0 {
// for performance reasons:
// only fetch state if some attributes need to be displayed for this resource type
res = resource.GetStates(res, providers)
for _, err := range result.Errors {
fmt.Fprint(os.Stderr, color.RedString("Error %s: %s\n", rType, err))
}

resources = append(resources, res...)
}

if len(resources) == 0 {
continue
}

printResources(resources, hasAttrs, attributes)
resource.PrintResources(resources, hasAttrs, attributes)
}

return 0
}

func printResources(resources []aws.Resource, hasAttrs map[string]bool, attributes []string) {
const padding = 3
w := tabwriter.NewWriter(os.Stdout, 0, 0, padding, ' ', tabwriter.TabIndent)

printHeader(w, attributes)

for _, r := range resources {
profile := `N/A`
if r.Profile != "" {
profile = r.Profile
}

fmt.Fprintf(w, "%s\t%s\t%s\t%s", r.Type, r.ID, profile, r.Region)

if r.CreatedAt != nil {
fmt.Fprintf(w, "\t%s", r.CreatedAt.Format("2006-01-02 15:04:05"))
} else {
fmt.Fprint(w, "\tN/A")
}

for _, attr := range attributes {
v := "N/A"

_, ok := hasAttrs[attr]
if ok {
var err error
v, err = resource.GetAttribute(attr, &r)
if err != nil {
log.WithFields(log.Fields{
"type": r.Type,
"id": r.ID}).WithError(err).Debug("failed to get attribute")
v = "error"
}
}

fmt.Fprintf(w, "\t%s", v)
}

fmt.Fprintf(w, "\t\n")
}

w.Flush()
fmt.Println()
}

func printHeader(w *tabwriter.Writer, attributes []string) {
fmt.Fprintf(w, "TYPE\tID\tPROFILE\tREGION\tCREATED")

for _, attribute := range attributes {
fmt.Fprintf(w, "\t%s", strings.ToUpper(attribute))
}

fmt.Fprintf(w, "\t\n")
}

func printHelp(fs *flag.FlagSet) {
fmt.Fprintf(os.Stderr, "\n"+strings.TrimSpace(help)+"\n")
fs.PrintDefaults()
Expand Down
84 changes: 84 additions & 0 deletions resource/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package resource

import (
"fmt"
"os"
"sync"
"time"

"github.com/apex/log"
"github.com/fatih/color"
awsls "github.com/jckuester/awsls/aws"
"github.com/jckuester/awsls/internal"
"github.com/jckuester/awstools-lib/aws"
"github.com/jckuester/awstools-lib/terraform"
"github.com/jckuester/terradozer/pkg/provider"
)

type UpdatedResources struct {
Resources []awsls.Resource
Errors []error
}

// ListInMultipleAccountsAndRegions lists resources of given resource type in parallel
// for multiple accounts and regions.
func ListInMultipleAccountsAndRegions(rType string, hasAttrs map[string]bool,
clients map[aws.ClientKey]awsls.Client, providers map[aws.ClientKey]provider.TerraformProvider) UpdatedResources {
var wg sync.WaitGroup
sem := internal.NewSemaphore(10)

resources := terraform.ResourcesThreadSafe{
Resources: []awsls.Resource{},
}

for key, client := range clients {
log.WithFields(log.Fields{
"type": rType,
"region": key.Region,
"profile": key.Profile,
"time": time.Now().Format("04:05.000"),
}).Debugf("start listing resources")

wg.Add(1)

go func(client awsls.Client) {
defer wg.Done()

// Acquire a semaphore so that we can limit concurrency
sem.Acquire()
defer sem.Release()

err := client.SetAccountID()
if err != nil {
fmt.Fprint(os.Stderr, color.RedString("Error %s: %s\n", rType, err))
return
}

res, err := awsls.ListResourcesByType(&client, rType)
if err != nil {
fmt.Fprint(os.Stderr, color.RedString("Error %s: %s\n", rType, err))
return
}

if len(hasAttrs) > 0 {
// for performance reasons:
// only fetch state if some attributes need to be displayed for this resource type
updatesRes, errs := terraform.UpdateStates(res, providers, 10)
res = updatesRes

resources.Lock()
resources.Errors = append(resources.Errors, errs...)
resources.Unlock()
}

resources.Lock()
resources.Resources = append(resources.Resources, res...)
resources.Unlock()
}(client)
}

// Wait until listing resources of this type completes for every account and region
wg.Wait()

return UpdatedResources{resources.Resources, resources.Errors}
}
Loading

0 comments on commit be41715

Please sign in to comment.